繁体   English   中英

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

[英]Asynchronous method in a C# class that executes a process

对此帖子有一个后续问题。 在我的版本中,我要进行异步处理。 这是我所拥有的:

    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 如果将其取出,可以编译,但是可以异步使用吗?
  2. 我在lambda表达式和相应的await process.StandardError.ReadToEndAsync()使用async关键字是否可以在进程lambda表达式内使用? await process.StandardError.ReadToEndAsync()可以? 此示例中 ,他们不在相应的行上使用async await ,所以我想知道他们如何摆脱它? 因为有人告诉我ReadToEnd方法正在阻塞,所以ReadToEndReadToEnd阻塞吗?
  3. 我对File.WriteAllText(LogFile, process.StandardOutput.ReadToEnd())调用是否将呈现整个方法阻塞? 如果是这样,我将如何避免呢?
  4. 异常处理有意义吗? 我是否应该知道在catch块中使用过的应用程序记录器方法Logger.InfoOutputWindow的任何详细信息?
  5. 最后,为什么在我遇到的所有示例中, process.Exited事件总是出现在process.Start()之前? 我可以将process.Start()放在process.Exited事件之前吗?

感谢任何想法,并预先感谢您的关注和关注。

编辑#1:

对于上面的#3,我有一个想法,部分基于下面@ File.WriteAllText(...)评论,因此我进行了更改,将File.WriteAllText(...)调用移到File.WriteAllText(...)else {}块内process.Exited 也许这解决了#3。

编辑#2:

我列出了最初的更改(现在代码段已更改),基本上删除了函数定义中的async关键字和await中的await关键字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
   <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)
  1. 您不需要方法签名上的async ,因为您不需要使用await 返回Task足够了。 调用方可以awaitTask -或不,它与您的方法无关。

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

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

  4. 您的异常处理看起来不错,但是我将在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处理程序。 不知道该如何处理,但是这种情况不太可能发生。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM