执行进程的C#类中的异步方法

我对这篇文章有一个后续问题。 在我的版本中,我有以下我想要异步。 这是我有的:

public virtual Task ExecuteAsync() { var tcs = new TaskCompletionSource(); string exe = Spec.GetExecutablePath(); string args = string.Format("--input1={0} --input2={1}", Input1, Input2); try { var process = new Process { EnableRaisingEvents = true, StartInfo = { UseShellExecute = false, FileName = exe, Arguments = args, RedirectStandardOutput = true, RedirectStandardError = true, WorkingDir = CaseDir } }; process.Exited += (sender, arguments) => { if (process.ExitCode != 0) { string errorMessage = process.StandardError.ReadToEndAsync(); tcs.SetResult(false); tcs.SetException(new InvalidOperationException("The process did not exit correctly. Error message: " + errorMessage)); } else { File.WriteAllText(LogFile, process.StandardOutput.ReadToEnd()); tcs.SetResult(true); } process.Dispose(); }; process.Start(); } catch (Exception e) { Logger.InfoOutputWindow(e.Message); tcs.SetResult(false); return tcs.Task; } return tcs.Task; } } 

这里的Spec, Input1, Input2, CaseDir, LogFile都是ExecuteAsync作为方法的类的成员。 可以这样使用它们吗? 我正在努力的部分是:

  1. 我似乎无法在方法定义中使用async关键字( public virtual async Task ExecuteAsync() )而没有警告我需要await关键字,而我在lambda表达式中有一个用于进程。 我甚至在方法定义中需要async关键字吗? 我已经看到了所谓的异步示例,他们不使用它,例如这个 。 如果我把它拿出来编译,但是我可以异步使用它吗?
  2. 我是否在lambda表达式中使用了async关键字并且相应的await process.StandardError.ReadToEndAsync()在进程lambda表达式中是否正常? 在这个例子中 ,他们不在相应的行使用async await ,所以我想知道他们是如何逃脱它的? 不会让它阻止,因为我被告知ReadToEnd方法是阻塞的吗?
  3. 我对File.WriteAllText(LogFile, process.StandardOutput.ReadToEnd())调用是否会导致整个方法阻塞? 如果是这样,我怎么能避免这种情况,如果可能的话?
  4. exception处理有意义吗? 我应该知道我在catch块中使用的应用程序记录器方法Logger.InfoOutputWindow的任何细节吗?
  5. 最后,为什么process.Exited事件总是出现在process.Start()之前我遇到过的所有例子? 我可以在process.Exited事件之前放入process.Start()吗?

感谢任何想法,并提前感谢您的兴趣和关注。

编辑#1:

对于上面的#3,我有一个想法,部分基于来自@ File.WriteAllText(...)评论,所以我做了一个更改,将File.WriteAllText(...)调用移到File.WriteAllText(...)else {}块内process.Exited File.WriteAllText(...)事件。 也许这解决了#3。

编辑#2:

我做了最初的更改列表(代码片段现在已更改),基本上删除了函数定义中的async关键字和process.Exited事件处理程序中基于@RenéVogt的原始注释。 尚未尝试下面的最新更改。 当我跑步时,我得到一个例外:

 A plugin has triggered error: System.InvalidOperationException; An attempt was made to transition a task to a final state when it had already completed. 

应用程序日志具有如下调用堆栈:

 UNHANDLED EXCEPTION: Exception Type: CLR Exception (v4) Exception Details: No message (.net exception object not captured) Exception Handler: Unhandled exception filter Exception Thread: Unnamed thread (id 29560) Report Number: 0 Report ID: {d80f5824-ab11-4626-930a-7bb57ab22a87} Native stack: KERNELBASE.dll+0x1A06D RaiseException+0x3D clr.dll+0x155294 clr.dll+0x15508E  (0x000007FE99B92E24)  (0x000000001AC86B00) Managed stack: at System.Threading.Tasks.TaskCompletionSource`1.SetException(Exception exception) at ..c__DisplayClass3.b__2(Object sender, EventArgs arguments) at System.Diagnostics.Process.RaiseOnExited() at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) at System.Threading._ThreadPoolWaitOrTimerCallback.PerformWaitOrTimerCallback(Object state, Boolean timedOut) 

  1. 您不需要方法签名上的async ,因为您不使用await 。 这足以返回一个Task 。 调用者可能awaitTask – 或者不是,它与您的方法无关。

  2. 不要在该lambda上使用async关键字,也不要在该lambda中使用异步ReadToEnd 。 很难预测如果你在真正完成之前从那个事件处理程序返回会发生什么。 无论如何你想要完成那个方法。 当进程退出时调用它,不需要使这个async

  3. 这与(2)中的相同。 我认为可以在此事件处理程序中“同步”执行此操作。 它只会阻止这个处理程序,但是在进程退出后调用处理程序,所以我猜你没关系。

  4. 您的exception处理看起来没问题,但我会在Exited事件处理程序中添加另一个try/catch块。 但这不是基于知识,而是基于经验,到处都可能出错:)


为了获得标准和错误输出的更好方法,我建议订阅ErrorDataReceivedOutputDataReceived事件并使用接收到的数据填充StringBuilder

在您的方法中,声明两个StringBuilders

 StringBuilder outputBuilder = new StringBuilder(); StringBuilder errorBuilder = new StringBuilder(); 

并在实例化process后立即订阅事件:

 process.OutputDataReceived += (sender, e) => outputBuilder.AppendLine(e.Data); process.ErrorDataReceived += (sender, e) => errorBuilder.AppendLine(e.Data); 

然后你只需要在调用process.Start() 之后立即调用这两个方法(之前它不起作用,因为stdout和stderr尚未打开):

 process.Start(); process.BeginErrorReadLine(); process.BeginOutputReadLine(); 

Exited事件处理程序中,然后可以分别调用outputBuilder.ToString() (或errorBuilder.ToString() )而不是ReadToEnd ,一切都应该正常工作。

不幸的是,还有一个缺点:如果进程非常快,可以在Begin*ReadLine调用之前调用Exited处理程序。 不知道如何处理,但不太可能发生。