C# process.WaitForExit() 异步

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/470256/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 04:40:35  来源:igfitidea点击:

process.WaitForExit() asynchronously

c#.netwinformsuser-interfaceasynchronous

提问by Dustin Getz

I want to wait for a process to finish, but process.WaitForExit()hangs my GUI. Is there an event-based way, or do I need to spawn a thread to block until exit, then delegate the event myself?

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

采纳答案by Timothy Carter

回答by Matt Brunell

Use System.Diagnostics.Process.Exited

System.Diagnostics.Process.Exited

回答by BFree

According to this linkthe WaitForExit() method is used to make the current thread wait until the associated process terminates. However, the Process does have an Exited event that you can hook into.

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

回答by MgSam

As of .NET 4.0/C# 5, it's nicer to represent this using the async pattern.

从 .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))
{
    var tcs = new TaskCompletionSource<object>();
    process.EnableRaisingEvents = true;
    process.Exited += (sender, args) => tcs.TrySetResult(null);
    if(cancellationToken != default(CancellationToken))
        cancellationToken.Register(tcs.SetCanceled);

    return tcs.Task;
}

Usage:

用法:

public async void Test() 
{
   var process = new Process("processName");
   process.Start();
   await process.WaitForExitAsync();

   //Do some fun stuff here...
}

回答by Dmitriy Nemykin

If you choose @MgSam answer, be aware, if you pass through WaitForExitAsyncsome CancellationToken, that will be automatically canceled after the specified delay, you can get an InvalidOperationException. To fix that, you need to change

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

cancellationToken.Register(tcs.SetCanceled);

to

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

P.S.: don't forget to dispose your CancellationTokenSourcein time.

PS:别忘了及时处理CancellationTokenSource

回答by Ryan

Here's an extension method that's slightly cleaner, because it cleans up the cancellation token registration and Exited event. It also handles the race condition edge case, where the process could end after it started, but before the Exited event was attached. It uses the new local functions syntax in C# 7.

这是一个稍微简洁的扩展方法,因为它清理了取消令牌注册和退出事件。它还处理竞争条件边缘情况,在这种情况下,进程可以在启动后但在附加 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;
        }
    }
}

EDIT:

编辑:

I have added TaskCreationOptions.RunContinuationsAsynchronouslyto the TaskCompletionSourceconstructor which should fix the deadlock that can occur when TrySetResult()attempts to run the continuation synchronously.

我已经添加TaskCreationOptions.RunContinuationsAsynchronouslyTaskCompletionSource构造函数中,它应该修复TrySetResult()尝试同步运行延续时可能发生的死锁。

The deadlock condition for this was actually really difficult to track down. It turns out that TaskCompletionSource.SetResult()runs continuations synchronously by default, which would cause all code under the await to run inside the SetResult(). This would normally not be an issue, because Process.Exitedis run on a threadpool thread. However, the entire Process.Exitedcallback runs within a lock on this, inside the new threadpool thread, where thisis the Processinstance. This can be seen here.

对此的僵局条件实际上很难追踪。事实证明,TaskCompletionSource.SetResult()默认情况下会同步运行延续,这将导致 await 下的所有代码在SetResult(). 这通常不是问题,因为它Process.Exited是在线程池线程上运行的。然而,整个Process.Exited回调在新线程池线程内的一个锁this运行,实例this是哪里Process。这可以在这里看到。

The process.Exited -= Process_Exited;also locks on this, which is due to the way the C# language specification implements event handlers. The end result is that two separate threadpool threads end up blocking on a lock on the Processinstance. Crazy! What's crazier is that if you don't have a synchronization context, the code immediately below the await WaitForExitAsync()may also run synchronously, so you'll end up running a huge amount of code within the lockinside of the Process.Exitedcallback. TaskCompletionSourceis pretty dangerous if you don't know about this behaviour!

process.Exited -= Process_Exited;还锁定这个,这是由于方式的C#语言规范器具的事件处理程序。最终结果是两个单独的线程池线程最终阻塞在Process实例上的锁上。疯狂的!更疯狂的是,如果您没有同步上下文,紧接其下方的代码await WaitForExitAsync()也可能同步运行,因此您最终会lockProcess.Exited回调内部运行大量代码。TaskCompletionSource如果您不了解这种行为,那将非常危险!

The solution is to add TaskCreationOptions.RunContinuationsAsynchronouslyto the TaskCompletionSourceconstructor. This allows TaskCompletionSource.SetResult()to return immediately, and everything will work "as expected".

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

回答by David Molnar

Ryan solution works good on windows. On OSX strange things happened it could be a Deadlock at tcs.TrySetResult()! There are 2 solutions:

Ryan 解决方案在 Windows 上运行良好。在 OSX 上发生了奇怪的事情,可能是死锁tcs.TrySetResult()!有2种解决方案:

First one:

第一:

Wrap tcs.TrySetResult()to a Task.Run():

包装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;
        }
    }

Conversation about this and more details: Calling TaskCompletionSource.SetResult in a non blocking manner

关于这个和更多细节的对话: 以非阻塞方式调用 TaskCompletionSource.SetResult

Second one:

第二个:

    public static async Task WaitForExitAsync(this Process process, CancellationToken cancellationToken)
    {
        while (!process.HasExited)
        {
            await Task.Delay(100, cancellationToken);
        }
    }

You can increase the Polling interval from 100 ms to more depending your application.

您可以根据您的应用将轮询间隔从 100 毫秒增加到更多。