Language: EN

csharp-tasks

What are Tasks and how to use them in C#

Tasks are a way to manage and execute asynchronous tasks in .NET. They allow for 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 is pending execution. This task may consist of several operations, and they 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 completed, its result can be retrieved.

Basic Creation and Use of Tasks

A Task can be created in several ways in C#. The most common methods 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 waiting task (in this case, 2 seconds) before finishing the execution of the Task.

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 you to wait for a Task to finish without blocking the main thread. Here’s a basic example:

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

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

Exception Handling in Tasks

Exception handling can be somewhat complicated. Exceptions in Tasks do not immediately propagate to the main thread; instead, they are 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($"Caught exception: {ex.Message}");
        }
    }
}

In this example, the exception thrown within the Task is caught 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 you to run multiple Tasks concurrently and waits until all are 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 you to continue 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.");
    }
}