我想等待一个进程完成,但process.WaitForExit()
挂起我的 GUI。是否有基于事件的方式,或者我是否需要生成一个线程以阻塞直到退出,然后自己委托事件?
9 回答
从 .NET 4.0/C# 5 开始,使用异步模式更好地表示这一点。
/// <summary>
/// Waits asynchronously for the process to exit.
/// </summary>
/// <param name="process">The process to wait for cancellation.</param>
/// <param name="cancellationToken">A cancellation token. If invoked, the task will return
/// immediately as canceled.</param>
/// <returns>A Task representing waiting for the process to end.</returns>
public static Task WaitForExitAsync(this Process process,
CancellationToken cancellationToken = default(CancellationToken))
{
if (process.HasExited) return Task.CompletedTask;
var tcs = new TaskCompletionSource<object>();
process.EnableRaisingEvents = true;
process.Exited += (sender, args) => tcs.TrySetResult(null);
if(cancellationToken != default(CancellationToken))
cancellationToken.Register(() => tcs.SetCanceled());
return process.HasExited ? Task.CompletedTask : tcs.Task;
}
用法:
public async void Test()
{
var process = new Process("processName");
process.Start();
await process.WaitForExitAsync();
//Do some fun stuff here...
}
process.EnableRaisingEvents = true;
process.Exited += [EventHandler]
这是一个稍微干净的扩展方法,因为它清除了取消令牌注册和退出事件。它还处理竞争条件边缘情况,在这种情况下,进程可以在它开始之后但在附加 Exited 事件之前结束。它使用 C# 7 中新的局部函数语法。
public static class ProcessExtensions
{
public static async Task WaitForExitAsync(this Process process, CancellationToken cancellationToken = default)
{
var tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
void Process_Exited(object sender, EventArgs e)
{
tcs.TrySetResult(true);
}
process.EnableRaisingEvents = true;
process.Exited += Process_Exited;
try
{
if (process.HasExited)
{
return;
}
using (cancellationToken.Register(() => tcs.TrySetCanceled()))
{
await tcs.Task.ConfigureAwait(false);
}
}
finally
{
process.Exited -= Process_Exited;
}
}
}
编辑:
我已经添加TaskCreationOptions.RunContinuationsAsynchronously
到构造函数中,它应该修复尝试同步运行延续TaskCompletionSource
时可能发生的死锁。TrySetResult()
这种死锁条件实际上很难追查。事实证明,TaskCompletionSource.SetResult()
默认情况下同步运行延续,这将导致等待下的所有代码在SetResult()
. 这通常不会成为问题,因为Process.Exited
它是在线程池线程上运行的。但是,整个Process.Exited
回调在一个 lock onthis
内运行,在新的线程池线程内,this
实例Process
在哪里。这可以在这里看到。
process.Exited -= Process_Exited;
也锁定this ,这是由于 C# 语言规范实现事件处理程序的方式。最终结果是两个单独的线程池线程最终阻塞了Process
实例上的锁。疯狂的!更疯狂的是,如果你没有同步上下文,紧接在它下面的代码也可能同步运行,所以你最终会在回调内部await WaitForExitAsync()
运行大量代码。如果你不知道这种行为是非常危险的!lock
Process.Exited
TaskCompletionSource
解决方案是添加TaskCreationOptions.RunContinuationsAsynchronously
到TaskCompletionSource
构造函数中。这允许TaskCompletionSource.SetResult()
立即返回,并且一切都将“按预期”工作。
如果您选择@MgSam 答案,请注意,如果您通过WaitForExitAsync
一些CancellationToken
,那将在指定延迟后自动取消,您可以获得一个InvalidOperationException
. 要解决这个问题,你需要改变
cancellationToken.Register(tcs.SetCanceled);
到
cancellationToken.Register( () => { tcs.TrySetCanceled(); } );
PS:别忘了及时处理CancellationTokenSource
。
根据此链接,WaitForExit() 方法用于使当前线程等待,直到相关进程终止。但是,Process 确实有一个您可以挂钩的 Exited 事件。
Ryan 解决方案在 Windows 上运行良好。在 OSX 上发生了奇怪的事情,它可能是死锁tcs.TrySetResult()
!有2个解决方案:
第一:
包装tcs.TrySetResult()
到 Task.Run():
public static async Task WaitForExitAsync(this Process process, CancellationToken cancellationToken = default)
{
var tcs = new TaskCompletionSource<bool>();
void Process_Exited(object sender, EventArgs e)
{
Task.Run(() => tcs.TrySetResult(true));
}
process.EnableRaisingEvents = true;
process.Exited += Process_Exited;
try
{
if (process.HasExited)
{
return;
}
using (cancellationToken.Register(() => Task.Run(() => tcs.TrySetCanceled())))
{
await tcs.Task;
}
}
finally
{
process.Exited -= Process_Exited;
}
}
关于这个和更多细节的对话: 以非阻塞方式调用 TaskCompletionSource.SetResult
第二个:
public static async Task WaitForExitAsync(this Process process, CancellationToken cancellationToken)
{
while (!process.HasExited)
{
await Task.Delay(100, cancellationToken);
}
}
您可以将轮询间隔从 100 毫秒增加到更多,具体取决于您的应用程序。
截至.NET5
目前, Process提供了WaitForExitAsync。 class
await process.WaitForExitAsync( token );
快速简单的解决方案:
async Task RunProcessWait(string Path)
{
Process MyProcess = Process.Start(Path);
while (!MyProcess.HasExited)
await Task.Delay(100);
}
await RunProcessWait("C:\...")
采用System.Diagnostics.Process.Exited