执行流程的C#类中的异步方法 [英] Asynchronous method in a C# class that executes a process

查看:263
本文介绍了执行流程的C#类中的异步方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我对此问题有后续问题发布.在我的版本中,我要进行异步处理.这是我所拥有的:

I have a follow-up question to this post. In my version, I have the following I want to make asynchronous. Here is what I have:

    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是方法的类的所有成员.这样使用它们可以吗?我苦苦挣扎的部分是:

Here Spec, Input1, Input2, CaseDir, LogFile are all members of the class of which the ExecuteAsync is a method. Is that OK to use them like that? The parts I am struggling with are:

  1. 我似乎在没有警告我需要await关键字的警告的情况下在方法定义(public virtual async Task<bool> ExecuteAsync())上使用async关键字,而我在该过程的lambda表达式中确实有一个.我甚至需要在方法定义中使用async关键字吗?我看过据说不使用异步示例的示例,例如.如果将其取出,可以编译,但是可以异步使用吗?
  2. 我在lambda表达式中使用async关键字以及在流程lambda表达式中使用相应的await process.StandardError.ReadToEndAsync() OK吗?在此示例中,他们在相应的行,所以我想知道他们如何摆脱它?既然我被告知方法ReadToEnd正在阻止,是否不会遗漏使其成为阻止对象?
  3. 我对File.WriteAllText(LogFile, process.StandardOutput.ReadToEnd())的调用是否会导致整个方法阻塞?如果是这样,我如何才能避免这种情况?
  4. 异常处理有意义吗?我应该知道在catch块中使用的应用程序记录器方法Logger.InfoOutputWindow的任何详细信息吗?
  5. 最后,为什么在我遇到的所有示例中,process.Exited事件总是出现在process.Start()之前?我可以将process.Start()放在process.Exited事件之前吗?
  1. I can't seem to use the async keyword at the method definition (public virtual async Task<bool> ExecuteAsync()) without a warning that I need an await keyword, whereas I do have one in the lambda expression for the process. Do I even need the async keyword at the method definition? I've seen supposedly asynchronous examples where they don't use it, e.g. this one. If I take it out it compiles, but can I use this asynchronously?
  2. Is my usage of the async keyword in the lambda expression and the corresponding await process.StandardError.ReadToEndAsync() OK inside the process lambda expression? In this example, they don't use async await at the corresponding line, so I wonder how they got away with it? Wouldn't leaving it out make it blocking, since I was told that the method ReadToEnd is blocking?
  3. Is my call to File.WriteAllText(LogFile, process.StandardOutput.ReadToEnd()) going to render the whole method blocking? If so, how can I avoid that, if at all possible?
  4. Does the exception handling make sense? Should I be aware of any details of the application logger method Logger.InfoOutputWindow which I've used in the catch block?
  5. Finally, why does the process.Exited event always appear before the process.Start() in all the examples I've come across? Can I put the process.Start() before the process.Exited event?

感谢任何想法,并预先感谢您的关注&注意.

Appreciate any ideas and thanks in advance for your interest & attention.

编辑#1:

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

For #3 above, I had an idea, based in part on comment from @René Vogt below, so I made a change to move the File.WriteAllText(...) call inside the else {} block of the process.Exited event. Perhaps this addresses #3.

编辑#2:

我做了一个初始更改列表(现在代码段已更改),根据@RenéVogt的原始注释,基本上删除了函数定义中的async关键字和process.Exited事件处理程序中的await关键字. .还没有在下面尝试过他的最新更改.跑步时,出现异常:

I made the initial list of changes (code snippet is changed now), basically removed both the async keyword at function definition and the await keyword in the process.Exited event handler based on original comments from @René Vogt. Haven't yet tried his most recent changes below. When I run, I get an exception:

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

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

The application log has call stack as follows:

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就足够了.调用方可能会await认为Task-否则,它与您的方法无关.

  1. You don't need the async on your method signature, because you don't use await. It's enough to return a Task. The caller may await that Task - or not, it has nothing to do with your method.

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

Don't use the async keyword on that lambda and don't use the asynchronous ReadToEnd inside that lambda. It's hard to predict what happens if you return from that event handler before it's really finished. And anyway you want to finish that method. It's called when the process exited, there is no need to make this async.

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

Here it's the same as in (2). I think it's ok to do this "synchronously" inside this event handler. It will only block this handler, but the handler is called after the process has exited, so I guess it's ok for you.

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

Your exception handling looks OK, but I would add another try/catch block inside the Exited event handler. But that's not based on knowledge, rather on experience that everywhere something can go wrong :)


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


For a better way to get the standard and error output, I suggest to subscribe to the ErrorDataReceived and OutputDataReceived events and fill StringBuilders with the received data.

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

In your method, declare two StringBuilders:

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

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

And subscribe to the events right after you instantiated process:

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

然后,您只需要在调用process.Start()后紧接调用这两个方法(因为stdout和stderr尚未打开,所以以前不起作用):

Then you only need to call these two methods right after you called process.Start() (it won't work before, because the stdout and stderr are not yet opened):

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

然后在您的Exited事件处理程序中,可以调用outputBuilder.ToString()(或分别为errorBuilder.ToString())而不是ReadToEnd,并且一切正常.

In your Exited event handler you can then call outputBuilder.ToString() (or errorBuilder.ToString() respectively) instead of ReadToEnd and everything should work fine.

也有一个缺点:如果处理过程非常快,则从理论上讲,您的Exited处理程序可能会在这些Begin*ReadLine调用之前被调用.不知道如何处理,但是这种情况不太可能发生.

Unfortunatly there is a drawback, too: if the process is very very fast, your Exited handler may theoritically be called before those Begin*ReadLine calls. Don't know how to handle that, but it's not very likely to happen.

这篇关于执行流程的C#类中的异步方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆