C# -

Tasks

Tasks in C# provide a powerful and flexible way to handle asynchronous programming and parallelism. They represent asynchronous operations and provide methods for managing and controlling these operations. This tutorial will cover the basics of tasks, how to create and manage them, common patterns, and best practices.


1. Understanding Tasks

A task represents an asynchronous operation. It can be used to perform background work, execute operations concurrently, and improve the responsiveness of applications. Tasks are managed by the Task Parallel Library (TPL), which provides a high-level abstraction for parallel programming.


2. Creating and Starting Tasks

You can create and start tasks using the Task class. Here's a simple example:

        
            using System;
using System.Threading.Tasks;

namespace TaskExamples;

class Program
{
    static void Main(string[] args)
    {
        Task task = new Task(DoWork);
        task.Start();
        task.Wait(); // Wait for the task to complete
        Console.WriteLine("Main thread continues to run...");
    }

    static void DoWork()
    {
        Console.WriteLine("Task is running...");
    }
}
        
    

3. Returning Values from Tasks

Tasks can return values using the Task<TResult> type. This allows you to perform asynchronous operations that produce results. Here's an example:

        
            using System;
using System.Threading.Tasks;

namespace AsyncExamples;

class Program
{
    static async Task Main(string[] args)
    {
        int result = await CalculateSumAsync(5, 10);
        Console.WriteLine($"Sum: {result}");
    }

    static async Task<int> CalculateSumAsync(int a, int b)
    {
        await Task.Delay(1000); // Simulate asynchronous work
        return a + b;
    }
}
        
    

4. Waiting for Tasks

You can wait for tasks to complete using the Wait method or the await keyword. Here's an example of waiting for a task to complete:

        
            using System;
using System.Threading.Tasks;

namespace TaskExamples;

class Program
{
    static async Task Main(string[] args)
    {
        Task task = DoWorkAsync();
        await task; // Wait for the task to complete
        Console.WriteLine("Main thread continues to run...");
    }

    static async Task DoWorkAsync()
    {
        await Task.Delay(1000); // Simulate asynchronous work
        Console.WriteLine("Task is running...");
    }
}
        
    

5. Task Continuations

Task continuations allow you to specify actions that should run after a task completes. This can be done using the ContinueWith method. Here's an example:

        
            using System;
using System.Threading.Tasks;

namespace TaskExamples;

class Program
{
    static void Main(string[] args)
    {
        Task task = Task.Run(() => DoWork())
            .ContinueWith(t => Console.WriteLine("Continuation task is running..."));
        task.Wait(); // Wait for the continuation task to complete
        Console.WriteLine("Main thread continues to run...");
    }

    static void DoWork()
    {
        Console.WriteLine("Task is running...");
    }
}
        
    

6. Cancelling Tasks

You can cancel tasks using a CancellationToken. This allows you to request the cancellation of a task and handle the cancellation within the task. Here's an example:

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

namespace TaskExamples;

class Program
{
    static async Task Main(string[] args)
    {
        CancellationTokenSource cts = new CancellationTokenSource();
        Task task = DoWorkAsync(cts.Token);
        
        cts.CancelAfter(500); // Cancel the task after 500 milliseconds
        
        try
        {
            await task;
        }
        catch (OperationCanceledException)
        {
            Console.WriteLine("Task was cancelled.");
        }
    }

    static async Task DoWorkAsync(CancellationToken token)
    {
        for (int i = 0; i < 10; i++)
        {
            token.ThrowIfCancellationRequested();
            await Task.Delay(200); // Simulate asynchronous work
            Console.WriteLine("Task is running...");
        }
    }
}
        
    

7. Exception Handling in Tasks

Exceptions in tasks can be handled using try-catch blocks. When an awaited task throws an exception, it is captured and can be re-thrown in the calling method. Here's an example:

        
            using System;
using System.Threading.Tasks;

namespace TaskExamples;

class Program
{
    static async Task Main(string[] args)
    {
        try
        {
            await DoWorkAsync();
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Exception: {ex.Message}");
        }
    }

    static async Task DoWorkAsync()
    {
        await Task.Delay(1000); // Simulate asynchronous work
        throw new InvalidOperationException("Something went wrong.");
    }
}
        
    

8. Running Tasks in Parallel

The Task Parallel Library (TPL) provides methods for running tasks in parallel, such as Parallel.For and Parallel.ForEach. These methods can significantly improve performance for certain types of operations. Here's an example:

        
            using System;
using System.Threading.Tasks;

namespace TaskExamples;

class Program
{
    static void Main(string[] args)
    {
        Parallel.For(0, 10, i =>
        {
            Console.WriteLine($"Task {i} is running...");
        });
    }
}
        
    

9. Best Practices for Using Tasks

Following best practices when using tasks can help you write efficient and maintainable asynchronous code. Here are some best practices to consider:


Conclusion

Tasks provide a powerful and flexible way to handle asynchronous programming and parallelism in C#. By understanding how to create, manage, and control tasks, and following best practices, you can build efficient and responsive applications.