执行流程的C#类中的异步方法 [英] Asynchronous method in a C# class that executes a process
问题描述
我对此问题有后续问题发布.在我的版本中,我要进行异步处理.这是我所拥有的:
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:
- 我似乎在没有警告我需要
await
关键字的警告的情况下在方法定义(public virtual async Task<bool> ExecuteAsync()
)上使用async关键字,而我在该过程的lambda表达式中确实有一个.我甚至需要在方法定义中使用async关键字吗?我看过据说不使用异步示例的示例,例如此.如果将其取出,可以编译,但是可以异步使用吗? - 我在lambda表达式中使用async关键字以及在流程lambda表达式中使用相应的
await process.StandardError.ReadToEndAsync()
OK吗?在此示例中,他们在相应的行,所以我想知道他们如何摆脱它?既然我被告知方法ReadToEnd
正在阻止,是否不会遗漏使其成为阻止对象? - 我对
File.WriteAllText(LogFile, process.StandardOutput.ReadToEnd())
的调用是否会导致整个方法阻塞?如果是这样,我如何才能避免这种情况? - 异常处理有意义吗?我应该知道在
catch
块中使用的应用程序记录器方法Logger.InfoOutputWindow
的任何详细信息吗? - 最后,为什么在我遇到的所有示例中,
process.Exited
事件总是出现在process.Start()
之前?我可以将process.Start()
放在process.Exited
事件之前吗?
- 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 anawait
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? - 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 useasync 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 methodReadToEnd
is blocking? - 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? - 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 thecatch
block? - Finally, why does the
process.Exited
event always appear before theprocess.Start()
in all the examples I've come across? Can I put theprocess.Start()
before theprocess.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)
推荐答案
-
您不需要在方法签名上使用
async
,因为您无需使用await
.返回Task
就足够了.调用方可能会await
认为Task
-否则,它与您的方法无关.
You don't need the
async
on your method signature, because you don't useawait
. It's enough to return aTask
. The caller mayawait
thatTask
- 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 :)
为获得标准和错误输出的更好方法,我建议订阅ErrorDataReceived
和OutputDataReceived
事件,并用接收到的数据填充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 StringBuilder
s 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屋!