Summary: in this tutorial, you will learn how to use the C# ContinueWith()
method of the Task
class to continue an asynchronous operation when once completes.
Introduction to the C# ContinueWith() method
The following program demonstrates how to use a Task
to run a time-consuming operation on a separate thread while still being able to retrieve the result of that operation in the main thread:
static int GetSquareNumber(int number)
{
Thread.Sleep(3000);
return number * number;
}
var result = 0;
var task = Task.Run(() => GetSquareNumber(10));
// block the main thread
result = task.Result;
while (result == 0)
{
Console.WriteLine("Waiting for the result...");
Thread.Sleep(1000);
}
Console.WriteLine(result);
Code language: C# (cs)
How it works.
First, define a method called GetSquareNumber()
that takes an integer and returns the square number of that number after a three-second delay. The method uses Thread.Sleep()
to pause the current thread for three seconds.
Next, the program creates a new Task
using the Task.Run()
method, which executes the GetSquareNumber()
method with an argument of 10 in a separate thread. The Task.Run()
returns a Task
object stored in the task
variable.
To retrieve the result of the GetSquareNumber()
method, the program uses task.Result
property, which blocks the main threads until the task is completed and the result is available.
The code inside the while
loop is not executed because the result
variable is zero once the execution reaches the while
loop.
The reason is that the task.Result
blocks the main thread until GetSquareNumber()
method returns the result and assigns it to the result
variable.
To create a continuation that will execute asynchronously once the target task has been completed, you use the static method ContinueWith()
of the Task
class.
For example:
static int GetSquareNumber(int number)
{
Thread.Sleep(3000);
return number * number;
}
// main thread
var result = 0;
// create a new task (in a different thread)
var task = Task.Run(() => GetSquareNumber(10));
// create a continuation
task.ContinueWith((task) =>
{
// a different thread
result = task.Result;
Console.WriteLine($"Result:{result}");
});
while (result == 0)
{
Console.WriteLine("Waiting for the result...");
Thread.Sleep(1000);
}
Code language: C# (cs)
Output:
Waiting for the result...
Waiting for the result...
Waiting for the result...
Result:100
Code language: plaintext (plaintext)
In this example, the main thread writes a message to the console 3 times, each per second until the task is completed.
The following ContinueWith()
method creates a continuation when the task has been completed. It executes a method that assigns the result of the task to the result
variable and writes it to the console.
Chaining Tasks
If you have several asynchronous operations that need to be performed in a specific order, and you want to start one operation after the completion of another operation, then you can use a technique known as asynchronous operation chaining or task chaining.
The following program demonstrates how to chain tasks using the ContinueWith()
method:
using static System.Console;
var t1 = Task.Run(() => 5);
var t2 = t1.ContinueWith(t => t.Result * 2);
var t3 = t2.ContinueWith(t => t.Result + 10);
t3.ContinueWith(t => WriteLine(t.Result));
// wait for the tasks to complete
Read();
Code language: C# (cs)
How it works.
First, create a new Task
called t1
that returns the integer value 5
using the Task.Run()
method.
Next, create a new Task
called t2
by calling the ContinueWith()
method on task t1
. The lambda expression passed to the ContinueWith()
method specifies that the result of t1
should be multiplied by 2
.
Then, create a new Task
called t3
by calling the ContinueWith()
method on t2
. The lambda expression specifies that the result of t2
should be incremented by 10
.
After that, use the ContinueWith()
method to wait for t3
to complete and write the final result to the console using the WriteLine()
method.
Finally, use the Read()
method to wait for the tasks to be completed before the program exits. This ensures that the output is displayed on the console before the program terminates.
Since the ContinueWith()
returns a Task
, you can chain them together like this to make the code more concise:
using static System.Console;
Task.Run(() => 5)
.ContinueWith(t => t.Result * 2)
.ContinueWith(t => t.Result + 10)
.ContinueWith(t => WriteLine(t.Result));
// wait for the tasks to complete
Read();
Code language: C# (cs)
Continuation options
Sometimes, you want to create a continuation based on whether the task is completed successfully or faulted or both. The ContinueWith() method has a second parameter with the type TaskContinuationOptionsthat that allows you to do it.
The following example shows how to use the ContinueWith method with the TaskContinuationOptions
using static System.Console;
static int GetRandomNumber(int min, int max)
{
if (min >= max)
{
throw new ArgumentException($"The {min} must less than {max}");
}
Thread.Sleep(1000);
return new Random().Next(min, max);
}
static int GetInt(string message)
{
int n = 0;
while (true)
{
Write(message);
string? input = ReadLine();
if (int.TryParse(input, out n))
{
return n;
}
}
}
int min = GetInt("Enter the min integer:");
int max = GetInt("Enter the max integer:");
var task = Task.Run(() => GetRandomNumber(min, max));
// continue if ran to completion
task.ContinueWith(t =>
{
WriteLine($"Result: {t.Result}");
},
TaskContinuationOptions.OnlyOnRanToCompletion);
// continue if only faulted
task.ContinueWith(t =>
{
WriteLine($"The task completed with the {task.Status} status");
},
TaskContinuationOptions.OnlyOnFaulted);
Read();
Code language: C# (cs)
How it works.
First, define the GetRandomNumber() method that returns a random number between min and max after a delay of one second. It throws an exception if the min is greater than the max:
static int GetRandomNumber(int min, int max)
{
if (min >= max)
{
throw new ArgumentException($"The {min} must less than {max}");
}
Thread.Sleep(1000);
return new Random().Next(min, max);
}
Code language: C# (cs)
Second, define the GetInt() method that returns an integer from user input:
static int GetInt(string message)
{
int n = 0;
while (true)
{
Write(message);
string? input = ReadLine();
if (int.TryParse(input, out n))
{
return n;
}
}
}
Code language: C# (cs)
Third, prompt the user for min and max integers:
int min = GetInt("Enter the min integer:");
int max = GetInt("Enter the max integer:");
Code language: C# (cs)
Fourth, create a task that returns a random integer between the min and the max:
var task = Task.Run(() => GetRandomNumber(min, max));
Code language: C# (cs)
If the min is less than max, the task will run to completion. Otherwise, it’ll be faulted.
Fifth, create a continuation if the task runs to completion by using the ContinueWith() method with the TaskContinuationOptions.OnlyOnRanToCompletion option:
task.ContinueWith(t =>
{
WriteLine($"Result: {t.Result}");
},
TaskContinuationOptions.OnlyOnRanToCompletion);
Code language: C# (cs)
Fifth, create a second continuation if the task is faulted by using the ContinueWith() method with the TaskContinuationOptions.OnlyOnFaulted option:
// continue if only faulted
task.ContinueWith(t =>
{
WriteLine($"The task completed with the {task.Status} status");
},
TaskContinuationOptions.OnlyOnFaulted);
Read();
Code language: C# (cs)
If you enter a valid min and max values, the first continuation executes that writes the random number to the console:
Enter the min integer:10
Enter the max integer:100
Result: 81
Code language: plaintext (plaintext)
Otherwise, the second continuation executes that writes an error message to the console:
Enter the min integer:100
Enter the max integer:10
The task completed with the Faulted status
Code language: plaintext (plaintext)
Summary
- Use the
ContinueWith()
method of theTask
class to create a continuation that executes asynchronously when theTask
has been completed. - Use the
TaskContinuationOptions
to create continuations according to the task’s status.