【问题标题】:Asynchronous method in a C# class that executes a process执行进程的 C# 类中的异步方法
【发布时间】:2016-05-20 14:56:34
【问题描述】:

我对@9​​87654321@ 有一个后续问题。在我的版本中,我有以下我想要异步的内容。这是我所拥有的:

    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&lt;bool&gt; ExecuteAsync()) 中使用 async 关键字,而不会警告我需要一个 await 关键字,而我在该过程的 lambda 表达式中确实有一个。我什至需要在方法定义中使用 async 关键字吗?我见过他们不使用它的所谓异步示例,例如this one。如果我把它拿出来编译,但我可以异步使用它吗?
  2. 我在 lambda 表达式中对 async 关键字的使用以及在进程 lambda 表达式中对应的await process.StandardError.ReadToEndAsync() 是否正常?在this example,他们没有在相应的行使用async await,所以我想知道他们是如何逃脱的?因为有人告诉我ReadToEnd 方法正在阻塞,所以不会让它阻塞吗?
  3. 我对@9​​87654332@ 的调用会导致整个方法阻塞吗?如果是这样,如果可能的话,我该如何避免这种情况?
  4. 异常处理有意义吗?我是否应该知道我在 catch 块中使用的应用程序记录器方法 Logger.InfoOutputWindow 的任何详细信息?
  5. 最后,为什么在我遇到的所有示例中process.Exited 事件总是出现在process.Start() 之前?我可以将process.Start() 放在process.Exited 事件之前吗?

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

编辑#1:

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

编辑 #2:

我制作了最初的更改列表(现在更改了代码 sn-p),基本上删除了函数定义中的 async 关键字和基于来自 @ 的原始 cmets 的 process.Exited 事件处理程序中的 await 关键字勒内·沃格特。尚未在下面尝试他最近的更改。当我运行时,我得到一个异常:

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)

【问题讨论】:

  • 这个问题似乎很宽泛。同一个问题有五个不同的问题?您应该一次实施、调试和寻求帮助。在继续下一个之前,请确保该部分正常工作。
  • 我同意 René 的观点,即您不应该调用 ReadToEndAsync(),但您还有另外两个问题:您从未设置过 RedirectStandardErrorRedirectStandardOutput,并等到进程退出再读取stdout 和 stderr 流是使进程死锁的好方法(这是一个典型的错误:输出流缓冲区填充,进程阻塞,您的代码,仅在进程 退出 时运行,永远无法读取流清空缓冲区)
  • 查看此答案,它可能会帮助您简化代码:stackoverflow.com/a/65804377/1653521

标签: c# asynchronous lambda process task


【解决方案1】:
  1. 您的方法签名上不需要async,因为您不使用await。返回一个Task 就足够了。调用者可能await 那个Task - 或者不是,它与你的方法无关。

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

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

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


为了更好地获取标准和错误输出,我建议订阅ErrorDataReceivedOutputDataReceived 事件并用接收到的数据填充StringBuilders。

在你的方法中,声明两个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 调用之前被调用。不知道如何处理,但不太可能发生。

【讨论】:

  • 感谢@René Vogt,将进行测试(请耐心等待,因为我需要在测试之前进行其他一些更改),然后根据需要对答案进行任何修改。
  • @squashed.bugaboo 慢慢来,但是我周末休息,不知道我是否有时间在星期一之前回复你(这里是UTC+1),但我会试试的。
  • @squashed.bugaboo 添加了一种从进程中读取标准/错误输出的替代方法,可以避免ReadToEnd的问题
  • @@René Vogt,我开始测试了。根据@Peter Duniho 的评论,我将 RedirectStandardOutput 和 RedirectStandardError 设置为 true。您是说即使进行了这些编辑,我也需要进行您最近提到的更改?当我测试时,它通过抛出“无效操作异常”而被应用程序崩溃。它的方式(在进行了最初讨论的更正之后),StandardOutput 应该没问题,因为它是在过程完成后调用的,对吗?所以只有标准错误应该是问题吗?我没有看到问题。
  • 嗨@René Vogt,我能够在初始 cmets 之后使用修复进行测试,不包括您最近的建议,它似乎可以按预期工作,即,当进程正在运行(异常不再出现,结果是由于其他问题)。因此,如果我在处理完成后处理输出并且添加了异常的内部错误,我仍然不知道为什么我需要执行上述所有操作。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-04-02
  • 1970-01-01
相关资源
最近更新 更多