[英]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方法的类的所有成员。 这样使用它们可以吗? 我正在努力的部分是:
public virtual async Task<bool> ExecuteAsync()
)上使用async关键字,而没有警告我需要一个await
关键字,而我在该过程的lambda表达式中确实有一个。 我甚至需要在方法定义中使用async关键字吗? 我看过据说不使用它们的异步示例,例如this 。 如果将其取出,可以编译,但是可以异步使用吗? await process.StandardError.ReadToEndAsync()
使用async关键字是否可以在进程lambda表达式内使用? await process.StandardError.ReadToEndAsync()
可以? 在此示例中 ,他们不在相应的行上使用async await
,所以我想知道他们如何摆脱它? 因为有人告诉我ReadToEnd
方法正在阻塞,所以ReadToEnd
它ReadToEnd
阻塞吗? File.WriteAllText(LogFile, process.StandardOutput.ReadToEnd())
调用是否将呈现整个方法阻塞? 如果是这样,我将如何避免呢? catch
块中使用过的应用程序记录器方法Logger.InfoOutputWindow
的任何详细信息? 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)
您不需要方法签名上的async
,因为您不需要使用await
。 返回Task
足够了。 调用方可以await
该Task
-或不,它与您的方法无关。
不要在该lambda上使用async
关键字,也不要在该lambda内使用异步ReadToEnd
。 很难预测如果事件处理程序真正完成之前返回该事件会发生什么。 而且无论如何,您都想完成该方法。 在进程退出时调用它,不需要使此async
。
这与(2)中的相同。 我认为可以在此事件处理程序中“同步”执行此操作。 它只会阻止该处理程序,但是在进程退出后将调用该处理程序,所以我认为这对您来说还可以。
您的异常处理看起来不错,但是我将在Exited
事件处理程序中添加另一个try/catch
块。 但这不是基于知识,而是基于经验,到处都会出错:)
为了获得标准和错误输出的更好方法,我建议订阅ErrorDataReceived
和OutputDataReceived
事件,并用接收到的数据填充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.