web-dev-qa-db-ja.com

process.WaitForExit()を非同期的に

プロセスが完了するのを待ちたいのですが、process.WaitForExit()がGUIをハングさせます。イベントベースの方法はありますか、または終了するまでブロックするスレッドを生成してから、自分でイベントを委任する必要がありますか?

40
Dustin Getz
28
Timothy Carter

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

使用法:

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

   //Do some fun stuff here...
}
81
MgSam

キャンセルトークンの登録とExitedイベントをクリーンアップするため、少しクリーンな拡張メソッドを次に示します。また、プロセスが開始した後、Exitedイベントがアタッチされる前に終了する可能性がある競合状態のEdgeケースも処理します。 C#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;
        }
    }
}
9
Ryan

@MgSam回答を選択した場合は、WaitForExitAsync some CancellationTokenを通過すると、指定された遅延後に自動的にキャンセルされ、InvalidOperationExceptionを取得できることに注意してください。それを修正するには、変更する必要があります

cancellationToken.Register(tcs.SetCanceled);

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

PS:CancellationTokenSourceを時間内に廃棄することを忘れないでください。

8
Dmitriy Nemykin

このリンクによると WaitForExit()メソッドは、関連するプロセスが終了するまで現在のスレッドを待機させるために使用されます。ただし、プロセスにはフックできるExitedイベントがあります。

4
BFree

ライアンソリューションは、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を呼び出す

2番目:

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

アプリケーションによっては、ポーリング間隔を100ミリ秒からさらに長くすることができます。

2
David Molnar

使用する System.Diagnostics.Process.Exited

1
Matt Brunell