asynchronous programming
Process.WaitForExit()
C# programming
.NET framework
task-based asynchronous pattern

Process.WaitForExit asynchronously

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

Process.WaitForExit() blocks the current thread until the child process ends. That is fine for a console utility, but it is a poor fit for UI code, servers, and any workflow where you want the calling thread to stay responsive while the external process runs.

The modern solution: WaitForExitAsync

In current .NET versions, the simplest answer is to use WaitForExitAsync.

csharp
1using System.Diagnostics;
2
3var process = new Process
4{
5    StartInfo = new ProcessStartInfo
6    {
7        FileName = "dotnet",
8        Arguments = "--info",
9        RedirectStandardOutput = true,
10        RedirectStandardError = true,
11        UseShellExecute = false
12    }
13};
14
15process.Start();
16
17string output = await process.StandardOutput.ReadToEndAsync();
18string error = await process.StandardError.ReadToEndAsync();
19await process.WaitForExitAsync();
20
21Console.WriteLine(process.ExitCode);
22Console.WriteLine(output);

This avoids blocking the calling thread while still giving you a proper awaitable completion point.

Why Task.Run(() => process.WaitForExit()) is not ideal

You will sometimes see code like this:

csharp
await Task.Run(() => process.WaitForExit());

That does make the calling code awaitable, but it still burns a thread pool thread just to sit and wait. It is usually a compatibility workaround, not the best design.

If WaitForExitAsync is available in your target framework, prefer it.

Handling output without deadlocks

A related problem is that people often wait for the process to exit before reading redirected output. That can deadlock if the child process fills its output buffer and blocks waiting for the parent to read.

A safer pattern is:

  • start the process
  • begin reading standard output and standard error
  • await process completion
  • await the read tasks
csharp
1using System.Diagnostics;
2
3var process = new Process
4{
5    StartInfo = new ProcessStartInfo
6    {
7        FileName = "ping",
8        Arguments = "localhost -n 2",
9        RedirectStandardOutput = true,
10        RedirectStandardError = true,
11        UseShellExecute = false,
12        CreateNoWindow = true
13    }
14};
15
16process.Start();
17
18Task<string> stdoutTask = process.StandardOutput.ReadToEndAsync();
19Task<string> stderrTask = process.StandardError.ReadToEndAsync();
20
21await process.WaitForExitAsync();
22
23string stdout = await stdoutTask;
24string stderr = await stderrTask;

The order matters when output is redirected.

Older frameworks without WaitForExitAsync

If you target an older framework, use the Exited event with a TaskCompletionSource.

csharp
1using System.Diagnostics;
2using System.Threading.Tasks;
3
4public static Task WaitForExitAsync(Process process)
5{
6    var tcs = new TaskCompletionSource<object?>();
7
8    process.EnableRaisingEvents = true;
9    process.Exited += (_, _) => tcs.TrySetResult(null);
10
11    if (process.HasExited)
12    {
13        tcs.TrySetResult(null);
14    }
15
16    return tcs.Task;
17}

Then use it like this:

csharp
process.Start();
await WaitForExitAsync(process);

That avoids blocking a worker thread and behaves more like the built-in async API.

Cancellation and timeouts

Sometimes you do not want to wait forever. Wrap the wait in a cancellation token or timeout policy.

csharp
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
await process.WaitForExitAsync(cts.Token);

If your target framework does not support the token overload, use Task.WhenAny with a delay task and kill the process if it exceeds the timeout.

Dispose and sequencing considerations

Remember that a Process object owns unmanaged resources. In production code, dispose it when you are done, usually with using or await using patterns where appropriate. Also make sure you do not dispose the process before asynchronous reads and exit waiting have completed, or you can end up with incomplete output capture and confusing exceptions.

Common Pitfalls

  • Wrapping synchronous waiting in Task.Run and assuming that makes it truly asynchronous.
  • Redirecting output but not reading it until after the process exits.
  • Forgetting UseShellExecute = false when redirecting standard output or error.
  • Not handling timeouts for processes that can hang indefinitely.
  • Using WaitForExit() on a UI thread and freezing the interface.

Summary

  • 'Process.WaitForExit() blocks the current thread.'
  • In modern .NET, use await process.WaitForExitAsync() instead.
  • Read redirected output asynchronously to avoid deadlocks.
  • On older frameworks, use the Exited event plus TaskCompletionSource.
  • Add cancellation or timeout handling for long-running external processes.

Related reading
Course
Intermediate
27 lessons
14 hours
OOD Fundamentals

Master object-oriented design from first principles, SOLID, design patterns, and classic interview problems with hands-on coding.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.