C# Task

Summary: in this tutorial, you’ll learn about the task-based asynchronous pattern (TAP) in C# and how to use the C# Task class to create asynchronous operations.

Introduction to the task-based asynchronous pattern (TAP)

C# introduced an asynchronous programming model (APM) that provides a way to perform I/O bound operations asynchronously.

The APM is based on callback concepts:

  • A method that represents an asynchronous operation that accepts a callback.
  • When the asynchronous operation completes, the method invokes the callback to notify the calling code.

Because APM is quite difficult to understand, C# introduced the event-based asynchronous pattern (EAP) that performs asynchronous operations using events.

Unlike the APM, in EAP, the method raises an event when the asynchronous operation completes instead of calling a callback.

EAP is easier to use and has better error handling than the APM. However, EAP is quite complex to use.

To solve this issue, C# introduced task-based asynchronous programming (TAP) that greatly simplifies asynchronous programming and makes it easier to write asynchronous code.

TAP consists of the following key components:

  • The Task class – represents an asynchronous operation.
  • The async / await keywords – define asynchronous methods and wait for the completion of asynchronous operations.
  • Task-based API – a set of classes that work seamlessly with the Task class and async/await keywords.

TAP has the following advantages:

  • Improved performance – TAP can improve an application’s performance by allowing it to perform I/O-bound operations asynchronously, freeing up the CPU for other tasks.
  • Simplified code – TAP allows you to write asynchronous code like synchronous code that makes it easy to understand.
  • Better resource management – TAP optimizes system resources by allowing applications to perform asynchronous operations without blocking threads.

In this tutorial, we’ll focus on the Task class, and how to use it to execute asynchronous operations.

The Task class

The Task class is a core concept of the TAP. It represents an asynchronous operation that can be executed in various ways.

Suppose you have a method that performs an asynchronous operation called GetRandomNumber() that returns a random number between 1 and 100 like this:

static int GetRandomNumber()
{
    Thread.Sleep(1000);
    int randomNumber = (new Random()).Next(1, 100);
    Console.WriteLine($"The random number is {randomNumber}");
    return randomNumber;
}Code language: C# (cs)

Unlike a regular function, the GetRandomNumber() uses the Thread.Sleep() to delay one second before returning a random number. The purpose of the Thread.Sleep() is to simulate an asynchronous operation that takes about one second to complete.

Running a task

To execute the GetRandomNumber() method asynchronously, you create a new Task object and call the GetRandomNumber() method in a lambda expression passed to the Task‘s constructor:

var task = new Task(() => GetRandomNumber());Code language: C# (cs)

and start executing the task by calling the Start() method of the Task object:

task.Start();Code language: C# (cs)

Put it all together:

static int GetRandomNumber()
{
    Thread.Sleep(1000);
    int randomNumber = (new Random()).Next(1, 100);
    Console.WriteLine($"The random number is {randomNumber}");
    return randomNumber;
}

var task = new Task(() => GetRandomNumber());
task.Start();

Console.WriteLine("Start the program...");

Console.ReadLine();Code language: C# (cs)

Output:

Start the program...
The random number is 65Code language: C# (cs)

Note that the task.Start() method doesn’t block the main thread therefore you see the following message first:

Start the program...Code language: C# (cs)

….before the random number:

The random number is 65Code language: C# (cs)

The Console.ReadLine() blocks the main thread until you press a key. It is used for waiting for the child thread scheduled by the Task object to complete.

If you don’t block the main thread, it’ll be terminated after the program displays the message “Start the program…”.

Notice that the Task constructor accepts many other options that you don’t need to worry about for now.

Behind the scenes, the program uses a thread pool for executing the asynchronous operation. The Start() method schedules the operation for execution.

To prove this, we can display the thread id and whether or not the thread belongs to the managed thread pool:

static int GetRandomNumber()
{
    var threadId = Thread.CurrentThread.ManagedThreadId;
    var threadPool = Thread.CurrentThread.IsThreadPoolThread;

    Console.WriteLine($"The thread #{threadId}, use a thread pool {threadPool}");

    Thread.Sleep(1000);
    int randomNumber = (new Random()).Next(1, 100);
    Console.WriteLine($"The random number is {randomNumber}");
    return randomNumber;
}

var task = new Task(() => GetRandomNumber());
task.Start();

Console.WriteLine("Start the program...");

Console.ReadLine();Code language: JavaScript (javascript)

Output:

Start the program...
The thread #5, use a thread pool True
The random number is 97Code language: PHP (php)

The output shows that the thread id is 5 and the thread belongs to a thread pool. Note that you likely see a different number.

Since the code for creating a Task object and starting it are quite verbose, you can shorten it by using the Run() static method of the Task class:

Task.Run(() => GetRandomNumber());Code language: C# (cs)

The Run() method queues operation (GetRandomNumber) to the thread pool for execution.

Similarly, you can use the StartNew() method of the Factory object of the Task class to create a new task and schedule its execution:

Task.Factory.StartNew(() => GetRandomNumber());Code language: C# (cs)

Getting the result from a task

The Run() method returns a Task<TResult> object that represents the result of the asynchronous operation.

In our example, the GetRandomNumber() returns an integer, therefore, the Task.Run() returns the Task<int> object:

Task<int> task = Task.Run(() => GetRandomNumber());Code language: C# (cs)

To get the returned number of the GetRandomNumber() method, you use the Result property of the task object:

task.ResultCode language: C# (cs)

Put it all together.

static int GetRandomNumber()
{
    Thread.Sleep(1000);
    int randomNumber = (new Random()).Next(1, 100);
    return randomNumber;
}


// The main thread is blocked
// until the result is available
Task<int> task = Task.Run(() => GetRandomNumber());

Console.WriteLine($"The random number is {task.Result}"); Code language: C# (cs)

Output:

The random number is 15Code language: C# (cs)

Summary

  • Use task-based asynchronous programming (TAP) for developing asynchronous programs.
  • Use the Task class to execute asynchronous operations.
Was this tutorial helpful ?