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?
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?
UPDATE: .NET 5 now includes Process.WaitForExitAsync() natively, you can find the implementation here. It's very similar to the below extension method.
Previous Answer:
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 cases, 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. The return value is the process return code.
public static class ProcessExtensions
{
public static async Task<int> WaitForExitAsync(this Process process, CancellationToken cancellationToken = default)
{
var tcs = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
void Process_Exited(object sender, EventArgs e)
{
tcs.TrySetResult(process.ExitCode);
}
try
{
process.EnableRaisingEvents = true;
}
catch (InvalidOperationException) when (process.HasExited)
{
// This is expected when trying to enable events after the process has already exited.
// Simply ignore this case.
// Allow the exception to bubble in all other cases.
}
using (cancellationToken.Register(() => tcs.TrySetCanceled()))
{
process.Exited += Process_Exited;
try
{
if (process.HasExited)
{
tcs.TrySetResult(process.ExitCode);
}
return await tcs.Task.ConfigureAwait(false);
}
finally
{
process.Exited -= Process_Exited;
}
}
}
}
As of .NET5 there is now a WaitForExitAsync provided by the Process class.
await process.WaitForExitAsync( token );
According to this link the 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.
Ryan solution works good on windows. On OSX strange things happened it could be a Deadlock at tcs.TrySetResult()! There are 2 solutions:
First one:
Wrap tcs.TrySetResult() to a 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
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.
Fast and simple solution:
async Task RunProcessWait(string Path)
{
Process MyProcess = Process.Start(Path);
while (!MyProcess.HasExited)
await Task.Delay(100);
}
await RunProcessWait("C:\...")