Process.WaitForExit() асинхронно

Я хочу дождаться завершения процесса, но process.WaitForExit() зависает мой графический интерфейс. Есть ли способ, основанный на событиях, или мне нужно создать поток для блокировки до выхода, а затем делегировать событие самостоятельно?

Ответ 2

Как и для .NET 4.0/С# 5, лучше представить это с использованием шаблона async.

/// <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;
}

Использование:

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

   //Do some fun stuff here...
}

Ответ 3

Здесь метод расширения, который немного чище, потому что он очищает регистрацию токена отмены и событие Exited. Он также обрабатывает крайний случай состояния гонки, где процесс может завершиться после того, как он начался, но до присоединения события Exited. Он использует новый синтаксис локальных функций в С# 7.

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

        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;
            }
        }
        finally
        {
            process.Exited -= Process_Exited;
        }
    }
}

Ответ 4

Если вы выберете ответ @MgSam, имейте в виду, что если вы пройдете через WaitForExitAsync some CancellationToken, который будет автоматически отменен после указанной задержки, вы можете получить InvalidOperationException. Чтобы исправить это, вам нужно изменить

cancellationToken.Register(tcs.SetCanceled);

к

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

P.S.: не забывайте своевременно распоряжаться своим CancellationTokenSource.

Ответ 5

В соответствии с этой ссылкой метод WaitForExit() используется для того, чтобы текущий поток ожидал, пока соответствующий процесс не завершится. Тем не менее, процесс имеет событие "Выход", в которое вы можете подключиться.

Ответ 6

Используйте System.Diagnostics.Process.Exited

Ответ 7

Решение Райана хорошо работает на окнах. На 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 мс до более в зависимости от вашего приложения.