Task Continuations
Continuations allow you to specify code that runs after a task completes. While async/await is preferred for most scenarios, understanding continuations helps with advanced patterns.
ContinueWith Basics
csharp1Task<int> task = ComputeValueAsync();23Task<string> continuation = task.ContinueWith(t =>4{5 int result = t.Result; // Get result from completed task6 return $"The answer is {result}";7});89string message = await continuation;
When to Use ContinueWith
| Use async/await | Use ContinueWith |
|---|---|
| Most scenarios | Dynamic task chaining |
| Clear control flow | Attaching to existing tasks |
| Exception handling | Fire-and-forget logging |
| Readable code | TaskCompletionSource patterns |
Task States in Continuations
The continuation receives the completed (antecedent) task:
csharp1task.ContinueWith(antecedent =>2{3 if (antecedent.IsCompletedSuccessfully)4 {5 Console.WriteLine($"Result: {antecedent.Result}");6 }7 else if (antecedent.IsFaulted)8 {9 Console.WriteLine($"Error: {antecedent.Exception?.Message}");10 }11 else if (antecedent.IsCanceled)12 {13 Console.WriteLine("Task was canceled");14 }15});
TaskContinuationOptions
Control when continuations run:
csharp1// Only run if task succeeded2task.ContinueWith(3 t => ProcessResult(t.Result),4 TaskContinuationOptions.OnlyOnRanToCompletion);56// Only run if task failed7task.ContinueWith(8 t => LogError(t.Exception),9 TaskContinuationOptions.OnlyOnFaulted);1011// Only run if task was canceled12task.ContinueWith(13 t => HandleCancellation(),14 TaskContinuationOptions.OnlyOnCanceled);
Common Options
csharp1// Run synchronously if possible (same thread)2TaskContinuationOptions.ExecuteSynchronously34// Run on a specific condition5TaskContinuationOptions.OnlyOnRanToCompletion6TaskContinuationOptions.OnlyOnFaulted7TaskContinuationOptions.OnlyOnCanceled8TaskContinuationOptions.NotOnRanToCompletion9TaskContinuationOptions.NotOnFaulted10TaskContinuationOptions.NotOnCanceled1112// Hint for long-running work13TaskContinuationOptions.LongRunning
Chaining Multiple Continuations
csharp1Task<int> pipeline = GetDataAsync()2 .ContinueWith(t => Parse(t.Result))3 .ContinueWith(t => Validate(t.Result))4 .ContinueWith(t => Transform(t.Result));56int result = await pipeline;
Continuation with Cancellation
csharp1var cts = new CancellationTokenSource();23task.ContinueWith(4 t => ProcessResult(t.Result),5 cts.Token, // Cancellation token6 TaskContinuationOptions.OnlyOnRanToCompletion,7 TaskScheduler.Default);89// Cancel continuation (not the original task)10cts.Cancel();
Unwrapping Nested Tasks
When continuation returns a task, use Unwrap():
csharp1// Without Unwrap: Task<Task<string>>2Task<Task<string>> nested = task.ContinueWith(t => FetchMoreDataAsync());34// With Unwrap: Task<string>5Task<string> unwrapped = task.ContinueWith(t => FetchMoreDataAsync()).Unwrap();67string result = await unwrapped;
Fire-and-Forget Logging
A practical use of ContinueWith:
csharp1public async Task<string> ProcessDataAsync(string input)2{3 var result = await DoWorkAsync(input);45 // Log without awaiting (fire-and-forget)6 _ = LogAsync(result).ContinueWith(t =>7 {8 if (t.IsFaulted)9 Console.WriteLine($"Logging failed: {t.Exception?.Message}");10 }, TaskContinuationOptions.OnlyOnFaulted);1112 return result;13}
Equivalent async/await Code
Most ContinueWith code can be written more clearly with async/await:
csharp1// ContinueWith version2Task result = task3 .ContinueWith(t => Step1(t.Result),4 TaskContinuationOptions.OnlyOnRanToCompletion)5 .ContinueWith(t => Step2(t.Result),6 TaskContinuationOptions.OnlyOnRanToCompletion);78// async/await version (preferred)9async Task ProcessAsync()10{11 try12 {13 var r1 = await task;14 var r2 = Step1(r1);15 var r3 = Step2(r2);16 }17 catch (Exception ex)18 {19 // Handle errors20 }21}
TaskCompletionSource with Continuations
Advanced pattern for converting callbacks to Tasks:
csharp1public Task<string> WrapCallbackApiAsync(string input)2{3 var tcs = new TaskCompletionSource<string>();45 LegacyApi.BeginOperation(input,6 result => tcs.SetResult(result),7 error => tcs.SetException(new Exception(error)));89 return tcs.Task;10}
Multiple Continuations on Same Task
You can attach multiple continuations:
csharp1Task<int> task = ComputeAsync();23// All three run when task completes4task.ContinueWith(t => LogResult(t.Result));5task.ContinueWith(t => UpdateCache(t.Result));6task.ContinueWith(t => NotifyClients(t.Result));78await task;
Exception Handling in Continuations
csharp1// Exception in continuation is stored in the continuation task2Task continuation = task.ContinueWith(t =>3{4 throw new InvalidOperationException("Continuation failed");5});67try8{9 await continuation;10}11catch (InvalidOperationException ex)12{13 // Caught here14}
Key Takeaways
ContinueWithspecifies code to run after task completion- Use
TaskContinuationOptionsto filter by outcome - async/await is preferred for most scenarios
- Continuations useful for fire-and-forget, logging, dynamic chains
- Use
Unwrap()when continuation returns a Task - Multiple continuations can attach to the same task