Task Concepts

In .NET, a Task represents an asynchronous operation. Tasks are fundamental to building responsive and scalable applications, especially in scenarios involving I/O-bound operations like network requests or file system access.

What is a Task?

A Task is an object that represents an ongoing operation. It can be used to track the progress of an asynchronous operation, wait for its completion, and retrieve its result or exception. Tasks are managed by the Task Parallel Library (TPL).

Tasks are a more flexible and powerful abstraction than older asynchronous patterns like the `Begin`/`End` methods. They integrate seamlessly with C#'s async and await keywords, making asynchronous code look and behave more like synchronous code.

Creating and Running Tasks

You can create a task in several ways. One common method is using the Task.Run method, which executes a delegate asynchronously on a thread pool thread.


using System;
using System.Threading.Tasks;

public class TaskExample
{
    public static async Task Main(string[] args)
    {
        Console.WriteLine("Starting an asynchronous operation...");

        // Create and run a task
        Task myTask = Task.Run(() =>
        {
            // Simulate a long-running operation
            System.Threading.Thread.Sleep(2000);
            Console.WriteLine("Asynchronous operation completed.");
        });

        Console.WriteLine("Main thread continues executing...");

        // Wait for the task to complete
        await myTask;

        Console.WriteLine("All operations finished.");
    }
}
            

For tasks that return a value, use Task<TResult>.


using System;
using System.Threading.Tasks;

public class TaskResultExample
{
    public static async Task Main(string[] args)
    {
        Console.WriteLine("Starting a task that returns a value...");

        Task<int> calculationTask = Task.Run(() =>
        {
            // Simulate a calculation
            System.Threading.Thread.Sleep(1500);
            return 42 * 2;
        });

        Console.WriteLine("Main thread is free to do other work...");

        // Get the result when the task is complete
        int result = await calculationTask;

        Console.WriteLine($"The result is: {result}");
    }
}
            

The `async` and `await` Keywords

The async and await keywords are syntactic sugar that simplifies working with tasks.

Using async and await is the recommended way to handle asynchronous operations in modern .NET development.

Task Status

A task has a status that indicates its current state. Common statuses include:

You can check the status of a task using the Status property.

Task Completion Sources

TaskCompletionSource<TResult> is useful when you need to manually signal the completion of a task. This is often seen when bridging older asynchronous patterns with the TPL or when creating custom asynchronous operations.


using System;
using System.Threading.Tasks;

public class TaskCompletionSourceExample
{
    public static Task<string> GetDataAsync(int id)
    {
        var tcs = new TaskCompletionSource<string>();

        // Simulate an asynchronous operation that might call back
        // In a real scenario, this might be a callback from a library
        // or a UI event handler.
        Task.Run(() =>
        {
            try
            {
                // Simulate fetching data
                System.Threading.Thread.Sleep(1000);
                if (id == 0)
                {
                    throw new ArgumentException("ID cannot be zero.");
                }
                tcs.SetResult($"Data for ID {id}");
            }
            catch (Exception ex)
            {
                tcs.SetException(ex);
            }
        });

        return tcs.Task;
    }

    public static async Task Main(string[] args)
    {
        try
        {
            string data = await GetDataAsync(10);
            Console.WriteLine($"Received: {data}");

            // This will throw an exception
            // string dataError = await GetDataAsync(0);
            // Console.WriteLine($"Received: {dataError}");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"An error occurred: {ex.Message}");
        }
    }
}
            

Task Cancellation

Tasks can be canceled using a CancellationTokenSource and a CancellationToken. This is crucial for preventing resource leaks and allowing users to abort long-running operations.


using System;
using System.Threading;
using System.Threading.Tasks;

public class TaskCancellationExample
{
    public static async Task PerformWorkWithCancellationAsync(CancellationToken cancellationToken)
    {
        Console.WriteLine("Work started...");
        for (int i = 0; i < 10; i++)
        {
            cancellationToken.ThrowIfCancellationRequested(); // Check for cancellation

            Console.WriteLine($"Doing work step {i}...");
            await Task.Delay(500); // Simulate work, allowing cancellation
        }
        Console.WriteLine("Work completed successfully.");
    }

    public static async Task Main(string[] args)
    {
        var cts = new CancellationTokenSource();
        var cancellationToken = cts.Token;

        Console.WriteLine("Starting task that can be cancelled. Press Enter to cancel.");

        var workTask = PerformWorkWithCancellationAsync(cancellationToken);

        // If Enter is pressed, cancel the task
        if (Console.ReadLine() == "")
        {
            Console.WriteLine("Cancelling task...");
            cts.Cancel();
        }

        try
        {
            await workTask;
            Console.WriteLine("Task finished normally.");
        }
        catch (OperationCanceledException)
        {
            Console.WriteLine("Task was cancelled.");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"An unexpected error occurred: {ex.Message}");
        }
    }
}