Language: EN

csharp-tasks

What are Tasks and how to use them in C#

Tasks are a way to manage asynchronous task execution in .NET. They allow performing background operations without blocking the main thread, resulting in a significant improvement in user experience and program efficiency.

Tasks are useful, for example

  • Input/Output (I/O) operations: such as reading or writing to a file, database access, or making HTTP requests.
  • Intensive processing: operations that consume a lot of CPU and could slow down the user interface in desktop or mobile applications.
  • Parallel execution: when multiple tasks need to be executed at the same time and wait for their results.

What are Tasks in C#?

A Task is an object that represents a task that is currently running or pending execution. This task can be composed of several operations and can be executed in parallel with other Tasks.

In C#, a Task is a class (System.Threading.Tasks.Task), and it has a state that indicates its progress; once it is complete, its result can be retrieved.

Basic Creation and Use of Tasks

A Task can be created in several ways in C#. The most common ways are:

  • Using the Task class directly
  • Utilizing async and await

Creating a Task

To create a basic Task, you can use the Task class and start a new task with Task.Run() or Task.Factory.StartNew().

For example, if you want to create a Task to perform a mathematical operation, you can write the following:

Task t = Task.Run(() => {
  int result = 1 + 2;
  
  Task.Delay(2000).Wait(); // Waits for 2 seconds
  
  Console.WriteLine("Result: " + result);
});

Here,

  • Task.Run: Creates and executes a Task that will run asynchronously.
  • Task.Delay: Simulates a wait task (in this case, 2 seconds) before finishing the Task execution.

Waiting for a Task to Complete

Once a Task has been created, it may be necessary to wait for it to complete before continuing with other operations.

To do this, you can use the Task’s Wait() method, which blocks the current thread until the Task completes.

Task t = Task.Run(() => {
  int result = 1 + 2;
  Console.WriteLine("Result: " + result);
});
t.Wait();

Using async and await

To simplify the handling of asynchronous Tasks, C# introduces the keywords async and await. The async modifier is placed in the declaration of a method, and await allows waiting for a Task to finish without blocking the main thread. Here is a basic example:

public static async Task Main()
{
	Console.WriteLine("Starting the asynchronous operation...");
	await AsynchronousOperation();
	Console.WriteLine("Operation completed.");
}

public static async Task AsynchronousOperation()
{
	await Task.Delay(2000); // Asynchronous wait of 2 seconds
	Console.WriteLine("Inside the asynchronous operation.");
}

Exception Handling in Tasks

Exception handling can become somewhat complicated. Exceptions in Tasks do not propagate immediately to the main thread; instead, they remain stored in the Task and must be caught using try-catch blocks along with await or task.Wait().

using System;
using System.Threading.Tasks;

public class Program
{
    public static async Task Main()
    {
        try
        {
            await Task.Run(() => { throw new InvalidOperationException("Error in the Task"); });
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Captured Exception: {ex.Message}");
        }
    }
}

In this example, the exception thrown inside the Task is captured by the try-catch block that wraps the await.

Executing Multiple Tasks: WhenAll and WhenAny

C# provides two very useful methods for working with multiple Tasks at the same time: Task.WhenAll and Task.WhenAny.

Task.WhenAll

This method allows executing several Tasks concurrently and waits until all of them complete.

using System;
using System.Threading.Tasks;

public class Program
{
    public static async Task Main()
    {
        Task task1 = Task.Delay(2000);
        Task task2 = Task.Delay(3000);

        await Task.WhenAll(task1, task2); // Waits for both tasks to finish
        Console.WriteLine("Both tasks completed.");
    }
}

Task.WhenAny

Task.WhenAny allows continuing execution as soon as any of the Tasks has finished. This method is useful when you need the quickest response among several operations.

using System;
using System.Threading.Tasks;

public class Program
{
    public static async Task Main()
    {
        Task task1 = Task.Delay(2000);
        Task task2 = Task.Delay(3000);

        await Task.WhenAny(task1, task2); // Continues when one task finishes
        Console.WriteLine("At least one task completed.");
    }
}