プロセスが完了するのを待ちたいのですが、process.WaitForExit()
がGUIをハングさせます。イベントベースの方法はありますか、または終了するまでブロックするスレッドを生成してから、自分でイベントを委任する必要がありますか?
process.EnableRaisingEvents = true;
process.Exited + = [EventHandler]
.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...
}
キャンセルトークンの登録と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;
}
}
}
@MgSam回答を選択した場合は、WaitForExitAsync
some CancellationToken
を通過すると、指定された遅延後に自動的にキャンセルされ、InvalidOperationException
を取得できることに注意してください。それを修正するには、変更する必要があります
cancellationToken.Register(tcs.SetCanceled);
に
cancellationToken.Register( () => { tcs.TrySetCanceled(); } );
PS:CancellationTokenSource
を時間内に廃棄することを忘れないでください。
このリンクによると WaitForExit()メソッドは、関連するプロセスが終了するまで現在のスレッドを待機させるために使用されます。ただし、プロセスにはフックできるExitedイベントがあります。
ライアンソリューションは、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ミリ秒からさらに長くすることができます。
使用する System.Diagnostics.Process.Exited