68

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

4

9 回答 9

122

从 .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...
}
于 2013-09-30T21:40:35.943 回答
34

process.EnableRaisingEvents = true;
process.Exited += [EventHandler]

于 2009-01-22T18:23:21.497 回答
24

这是一个稍微干净的扩展方法,因为它清除了取消令牌注册和退出事件。它还处理竞争条件边缘情况,在这种情况下,进程可以在它开始之后但在附加 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()运行大量代码。如果你不知道这种行为是非常危险的!lockProcess.ExitedTaskCompletionSource

解决方案是添加TaskCreationOptions.RunContinuationsAsynchronouslyTaskCompletionSource构造函数中。这允许TaskCompletionSource.SetResult()立即返回,并且一切都将“按预期”工作。

于 2018-05-22T07:01:54.757 回答
10

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

cancellationToken.Register(tcs.SetCanceled);

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

PS:别忘了及时处理CancellationTokenSource

于 2015-10-07T14:20:47.640 回答
5

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

于 2009-01-22T18:21:27.477 回答
4

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 毫秒增加到更多,具体取决于您的应用程序。

于 2019-05-08T10:36:25.750 回答
3

截至.NET5目前, Process提供了WaitForExitAsync class

await process.WaitForExitAsync( token );
于 2021-04-23T13:08:23.610 回答
2

快速简单的解决方案:

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

await RunProcessWait("C:\...")
于 2020-07-20T14:27:52.057 回答
-2

采用System.Diagnostics.Process.Exited

于 2009-01-22T18:19:02.940 回答