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

时间:2016-05-20 14:56:35

标签: c# asynchronous lambda process task

我有this post的后续问题。在我的版本中,我有以下我想要异步。这就是我所拥有的:

    public virtual Task<bool> ExecuteAsync()
    {
        var tcs = new TaskCompletionSource<bool>();
        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. 我似乎无法在方法定义(public virtual async Task<bool> ExecuteAsync())中使用async关键字而没有警告我需要await关键字,而我在lambda表达式中有一个用于该过程。我甚至在方法定义中需要async关键字吗?我已经看到了他们不使用它的所谓异步示例,例如: this one。如果我把它拿出来编译,但是我可以异步使用它吗?
  2. 我是否在lambda表达式中使用了async关键字,并且在进程lambda表达式中使用了相应的await process.StandardError.ReadToEndAsync() OK?在this example中,他们不会在相应的行使用async await,所以我想知道他们是如何逃脱它的?不会让它阻止它,因为我被告知方法ReadToEnd正在阻止?
  3. 我对File.WriteAllText(LogFile, process.StandardOutput.ReadToEnd())的调用是否会导致整个方法被阻止?如果是这样,我怎么能避免这种情况,如果可能的话?
  4. 异常处理有意义吗?我是否应该了解我在Logger.InfoOutputWindow块中使用的应用程序记录器方法catch的任何详细信息?
  5. 最后,为什么process.Exited事件总是出现在我遇到的所有示例中的process.Start()之前?我可以将process.Start()放在process.Exited事件之前吗?
  6. 感谢任何想法并提前感谢您的兴趣和利益;注意。

    编辑#1:

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

    编辑#2:

    我制作了最初的更改列表(现在更改了代码段),基本上删除了函数定义中的await关键字和基于process.Exited事件处理程序中的A plugin has triggered error: System.InvalidOperationException; An attempt was made to transition a task to a final state when it had already completed. 关键字来自@RenéVogt的原始评论。尚未尝试下面的最新更改。当我跑步时,我得到一个例外:

    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
       <unknown/managed> (0x000007FE99B92E24)
       <unknown/managed> (0x000000001AC86B00)
    Managed stack:
       at System.Threading.Tasks.TaskCompletionSource`1.SetException(Exception exception)
       at <namespace>.<MyClass>.<>c__DisplayClass3.<ExecuteAsync>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)
    

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

    session

1 个答案:

答案 0 :(得分:4)

  1. 您的方法签名不需要async,因为您不使用await。这足以返回Task。来电者await可以Task或不是,与您的方法无关。

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

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

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

  5. 为了更好地获取标准和错误输出,我建议订阅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,一切都应该正常。

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