异步地处理

我希望等待一个进程完成,但是 Process.WaitForExit()挂起了我的 GUI。是否有基于事件的方法,或者我需要产生一个线程来阻塞直到退出,然后自己委托事件?

44908 次浏览

使用 System.Diagnostics.Process.Exited

根据这个链接 ,WaitForExit ()方法用于使当前线程等待,直到关联的进程终止。但是,Process 确实有一个可以挂钩到的 Exted 事件。

在.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...
}

如果您选择@MgSam 答案,请注意,如果您通过 WaitForExitAsync一些 CancellationToken,这将自动取消后,指定的延迟,您可以得到一个 InvalidOperationException。要解决这个问题,你需要改变

cancellationToken.Register(tcs.SetCanceled);

cancellationToken.Register( () => { tcs.TrySetCanceled(); } );

附注: 别忘了及时处理掉你的 CancellationTokenSource

更新: 。NET 5现在包含了 Process.WaitForExitAsync()本机,您可以找到实现 给你。它与下面的扩展方法非常相似。

上次回答:

下面的扩展方法稍微简单一些,因为它清除了取消令牌注册和退出事件。它还处理竞态条件边界情况,其中流程可以在启动后结束,但在附加 Exted 事件之前结束。它使用 C # 7中新的本地函数语法。返回值是进程返回代码。

public static class ProcessExtensions
{
public static async Task<int> WaitForExitAsync(this Process process, CancellationToken cancellationToken = default)
{
var tcs = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);


void Process_Exited(object sender, EventArgs e)
{
tcs.TrySetResult(process.ExitCode);
}


try
{
process.EnableRaisingEvents = true;
}
catch (InvalidOperationException) when (process.HasExited)
{
// This is expected when trying to enable events after the process has already exited.
// Simply ignore this case.
// Allow the exception to bubble in all other cases.
}


using (cancellationToken.Register(() => tcs.TrySetCanceled()))
{
process.Exited += Process_Exited;


try
{
            

if (process.HasExited)
{
tcs.TrySetResult(process.ExitCode);
}


return await tcs.Task.ConfigureAwait(false);
}
finally
{
process.Exited -= Process_Exited;
}
}
}
}

Ryan 解决方案在窗口上运行良好。在 OSX 奇怪的事情发生它可能是一个僵局在 tcs.TrySetResult()!有两种解决办法:

第一个:

tcs.TrySetResult()包装到任务中。 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毫秒增加到更多。

快速简单的解决方案:

async Task RunProcessWait(string Path)
{
Process MyProcess = Process.Start(Path);
while (!MyProcess.HasExited)
await Task.Delay(100);
}


await RunProcessWait("C:\...")

.NET5开始,现在有一个由 过程 class提供的 WaitForExitAsync

await process.WaitForExitAsync( token );