C# ContinueWith

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:100Code 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: 81Code 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 statusCode language: plaintext (plaintext)

Summary

  • Use the ContinueWith() method of the Task class to create a continuation that executes asynchronously when the Task has been completed.
  • Use the TaskContinuationOptions to create continuations according to the task’s status.
Was this tutorial helpful ?