High-Performance .NET: Async, Multithreading, and Parallel Programming Tasks in .Net Created: 22 Jan 2026 Updated: 22 Jan 2026

Task Continuations in C#

In complex asynchronous workflows, a single task often isn't enough. You frequently need to coordinate multiple operations, either waiting for all of them to finish or acting as soon as any one of them yields a result. This is where multi-task continuations come into play.

By using the TaskFactory or the modern Task.WhenAll / Task.WhenAny wrappers, you can create sophisticated logic that orchestrates parallel work efficiently.

1. Waiting for Every Task: ContinueWhenAll

The ContinueWhenAll method is used when a follow-up action depends on the successful (or attempted) completion of a group of tasks. It ensures that the continuation only triggers once the entire batch has reached a final state.

Common Use Case: Aggregating data from multiple independent microservices to generate a final report.

Code Example: Batch Data Processing

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

class MultiTaskAll
{
static void Main()
{
Console.WriteLine("Initializing parallel data fetch...");

// Define a set of tasks fetching data from different regions
var taskList = new List<Task<string>>
{
Task.Run(() => { Thread.Sleep(1200); return "Region A Data"; }),
Task.Run(() => { Thread.Sleep(800); return "Region B Data"; }),
Task.Run(() => { Thread.Sleep(2000); return "Region C Data"; })
};

// Create a continuation that runs only after ALL tasks complete
Task reportTask = Task.Factory.ContinueWhenAll(taskList.ToArray(), antecedents =>
{
Console.WriteLine("\n--- Final Report Summary ---");
foreach (var t in antecedents)
{
// Each antecedent is passed in, allowing us to access results
Console.WriteLine($"Processed: {((Task<string>)t).Result}");
}
Console.WriteLine("Report generation finalized.");
});

reportTask.Wait();
}
}

2. Reacting to the Fastest Task: ContinueWhenAny

The ContinueWhenAny method triggers its continuation as soon as the first task in a collection completes. The remaining tasks continue to run in the background, but the continuation logic proceeds immediately with the result of the winner.

Common Use Case: Querying multiple redundant servers for the same information and using whichever response arrives first to reduce latency.

Code Example: Redundant API Call Simulation

using System;
using System.Threading;
using System.Threading.Tasks;

class MultiTaskAny
{
static void Main()
{
Console.WriteLine("Querying redundant servers...");

var server1 = Task.Run(() => {
Thread.Sleep(1500); // Slower server
return "Response from Server 1 (Primary)";
});

var server2 = Task.Run(() => {
Thread.Sleep(500); // Faster server
return "Response from Server 2 (Backup)";
});

// Continue as soon as ANY server responds
Task winnerTask = Task.Factory.ContinueWhenAny(new[] { server1, server2 }, winner =>
{
// The 'winner' parameter is the specific task that finished first
string result = ((Task<string>)winner).Result;
Console.WriteLine($"\nFastest response received: {result}");
});

winnerTask.Wait();
Console.WriteLine("Moving forward with the fastest data.");
}
}

Key Differences at a Glance

MethodExecution TriggerPrimary Purpose
ContinueWhenAllAfter all antecedents finish.Aggregation, batching, and synchronization.
ContinueWhenAnyAfter the first antecedent finishes.Latency reduction, timeouts, and redundancy.

Best Practice Tip: The "Modern" Way

While Task.Factory.ContinueWhenAll/Any is powerful, modern C# code often uses Task.WhenAll or Task.WhenAny combined with await. This allows you to write the logic in a more readable, linear style while still utilizing the same underlying TPL engine.

Share this lesson: