MSDN Documentation

Introduction to the System.Threading.Tasks Namespace

The System.Threading.Tasks namespace provides a set of high-level APIs for asynchronous programming in .NET. It is the foundation for asynchronous operations, enabling applications to remain responsive and efficient by offloading work to background threads.

Tasks are the fundamental building blocks for representing asynchronous operations. They allow you to manage, schedule, and await the completion of work that runs concurrently with your application's main thread. This namespace is crucial for modern .NET development, especially for applications that perform I/O-bound or CPU-bound operations.

Key Concepts

The System.Threading.Tasks namespace revolves around several core concepts:

  • Tasks: Represent an asynchronous operation.
  • TaskScheduler: Manages the scheduling of tasks onto threads.
  • Cancellation: Mechanisms to signal and respond to cancellation requests.
  • Continuations: Operations that execute after a task completes.
  • Task Factories: Utilities for creating and configuring tasks.

The Task Class

The Task class represents an asynchronous operation that does not return a value. It is the most fundamental type in this namespace.

Creating Tasks

Tasks can be created in several ways:

  • Using Task.Run() to execute a delegate on a thread pool thread.
  • Using Task.Factory.StartNew() for more control over task creation.
  • As the result of an `async` method that returns Task.

// Using Task.Run
Task longRunningTask = Task.Run(() => {
    // Simulate a long-running operation
    System.Threading.Thread.Sleep(2000);
    Console.WriteLine("Long running operation completed.");
});

// Using Task.Factory.StartNew
Task anotherTask = Task.Factory.StartNew(() => {
    Console.WriteLine("Another task running on thread: " + Thread.CurrentThread.ManagedThreadId);
});
                

Awaiting Tasks

The await operator is used to asynchronously wait for a task to complete. When you `await` a task, the method execution is paused until the awaited task finishes, and control is returned to the caller. This allows the thread to perform other work.


public async Task PerformAsyncOperation()
{
    Console.WriteLine("Starting async operation...");
    Task myTask = Task.Run(() => {
        System.Threading.Thread.Sleep(3000);
        Console.WriteLine("Inner task completed.");
    });

    await myTask; // Execution pauses here until myTask completes

    Console.WriteLine("Async operation finished.");
}
                

Task States

A Task can be in various states:

  • Created: The task has been instantiated but not yet scheduled or executed.
  • WaitingForActivation: The task has been scheduled and is waiting to be executed.
  • Running: The task is currently executing.
  • WaitingToRun: The task is scheduled and ready to run, but the scheduler is busy.
  • WaitingForChildrenToComplete: The task is running, and it is waiting for one or more child tasks to complete.
  • RanToCompletion: The task completed successfully.
  • Canceled: The task was canceled.
  • Faulted: The task terminated due to an unhandled exception.

Cancellation

Tasks can be canceled using CancellationTokenSource and CancellationToken. The operation being performed must explicitly check for cancellation requests.


public async Task CancellableOperation(CancellationToken cancellationToken)
{
    for (int i = 0; i < 10; i++)
    {
        cancellationToken.ThrowIfCancellationRequested(); // Check for cancellation
        Console.WriteLine($"Working... {i}");
        await Task.Delay(500, cancellationToken);
    }
    Console.WriteLine("Operation finished successfully.");
}

// In another part of your code:
var cts = new CancellationTokenSource();
Task operationTask = Task.Run(() => CancellableOperation(cts.Token));

// To cancel:
// cts.Cancel();
                

Exception Handling

Exceptions thrown by tasks are captured and stored in the Task.Exception property. When you await a faulted task, the exception is re-thrown.


public async Task HandleTaskExceptions()
{
    Task taskWithError = Task.Run(() => {
        throw new InvalidOperationException("Something went wrong!");
    });

    try
    {
        await taskWithError;
    }
    catch (AggregateException ae)
    {
        // Handle exceptions. A task can have multiple exceptions.
        foreach (var ex in ae.InnerExceptions)
        {
            Console.WriteLine($"Caught exception: {ex.Message}");
        }
    }
}
                

The Task<TResult> Class

This generic class represents an asynchronous operation that returns a value of type TResult.


public async Task<string> GetDataAsync()
{
    await Task.Delay(1000); // Simulate network latency
    return "Some retrieved data";
}

public async Task ProcessData()
{
    string result = await GetDataAsync();
    Console.WriteLine("Received: " + result);
}
                

TaskFactory

Task.Factory provides methods for creating and configuring tasks, offering more fine-grained control than Task.Run, such as specifying task creation options, scheduling options, and continuations.

TaskCompletionSource<TResult>

This class allows you to create a task whose result or completion status can be set asynchronously from outside the task itself. It's often used to bridge older asynchronous APIs (like event-based or callback-based ones) with the Task-based Asynchronous Pattern (TAP).


public class AsyncHelper
{
    public static Task<int> GetNumberAsync()
    {
        var tcs = new TaskCompletionSource<int>();
        // Simulate an operation that will eventually set the result
        Task.Run(async () => {
            await Task.Delay(1500);
            tcs.SetResult(42); // Set the result
        });
        return tcs.Task;
    }
}
                

Parallel Programming with Tasks

The System.Threading.Tasks namespace is also integral to parallel programming in .NET, enabling you to easily run multiple operations concurrently.

Parallel Loops

Parallel.For and Parallel.ForEach allow you to execute loop iterations in parallel.


Parallel.For(0, 100, i => {
    Console.WriteLine($"Processing item {i} on thread {Thread.CurrentThread.ManagedThreadId}");
});
                

Parallel Methods

Parallel.Invoke allows you to invoke multiple delegates concurrently.


Parallel.Invoke(
    () => Console.WriteLine("Task 1"),
    () => Console.WriteLine("Task 2"),
    () => Console.WriteLine("Task 3")
);
                

Best Practices

  • Use async and await for I/O-bound operations to keep the UI responsive and to efficiently utilize threads.
  • Use Task.Run for CPU-bound operations that you want to offload from the main thread.
  • Always handle exceptions by using try-catch blocks around await expressions or by inspecting the Task.Exception property.
  • Use CancellationToken to allow operations to be canceled gracefully.
  • Avoid blocking on tasks using .Result or .Wait(), as this can lead to deadlocks, especially in UI or ASP.NET contexts.