Skip to content

C# 异步编程

概述

异步编程允许您编写非阻塞代码,可以并发处理多个操作。C# 为此提供了强大的 async/await 关键字。

基本异步概念

同步与异步

csharp
public class SynchronousExample
{
    public void DownloadFiles()
    {
        // 同步 - 阻塞线程
        string file1 = DownloadFile("file1.txt");
        string file2 = DownloadFile("file2.txt");
        string file3 = DownloadFile("file3.txt");

        Console.WriteLine("All files downloaded");
    }

    private string DownloadFile(string filename)
    {
        // 模拟文件下载(阻塞 2 秒)
        System.Threading.Thread.Sleep(2000);
        return $"Content of {filename}";
    }
}

public class AsynchronousExample
{
    public async Task DownloadFilesAsync()
    {
        // 异步 - 不阻塞线程
        Task<string> task1 = DownloadFileAsync("file1.txt");
        Task<string> task2 = DownloadFileAsync("file2.txt");
        Task<string> task3 = DownloadFileAsync("file3.txt");

        // 等待所有任务完成
        string[] results = await Task.WhenAll(task1, task2, task3);

        Console.WriteLine("All files downloaded");
    }

    private async Task<string> DownloadFileAsync(string filename)
    {
        // 模拟异步文件下载
        await Task.Delay(2000);
        return $"Content of {filename}";
    }
}

Async 和 Await 关键字

csharp
public class AsyncAwaitBasics
{
    // 返回 Task 的异步方法
    public async Task<string> GetDataAsync()
    {
        Console.WriteLine("Starting data retrieval...");

        // 等待异步操作
        string data = await FetchDataFromServerAsync();

        Console.WriteLine("Data retrieved successfully");
        return data;
    }

    // 返回 Task<T> 的异步方法
    public async Task<int> CalculateAsync(int a, int b)
    {
        Console.WriteLine("Starting calculation...");

        // 模拟异步计算
        await Task.Delay(1000);

        int result = a + b;
        Console.WriteLine($"Calculation complete: {result}");
        return result;
    }

    // 异步 void 方法(谨慎使用)
    public async void FireAndForget()
    {
        try
        {
            await SomeAsyncOperation();
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error in fire-and-forget: {ex.Message}");
        }
    }

    private async Task<string> FetchDataFromServerAsync()
    {
        await Task.Delay(2000);
        return "Server data";
    }

    private async Task SomeAsyncOperation()
    {
        await Task.Delay(500);
        Console.WriteLine("Async operation completed");
    }
}

基于任务的异步模式 (TAP)

创建异步方法

csharp
public class TaskBasedAsync
{
    // 返回 Task 的方法
    public async Task ProcessDataAsync()
    {
        Console.WriteLine("Processing data...");
        await Task.Delay(1000);
        Console.WriteLine("Data processed");
    }

    // 返回 Task<T> 的方法
    public async Task<int> CalculateSumAsync(int a, int b)
    {
        await Task.Delay(500);
        return a + b;
    }

    // 使用 Task.Run 处理 CPU 密集型工作
    public async Task<long> CalculateFactorialAsync(int n)
    {
        return await Task.Run(() =>
        {
            long result = 1;
            for (int i = 2; i <= n; i++)
            {
                result *= i;
            }
            return result;
        });
    }
}

任务创建和执行

csharp
public class TaskCreation
{
    public async Task DemonstrateTaskCreation()
    {
        // 创建并启动任务
        Task task1 = Task.Run(() =>
        {
            Console.WriteLine("Task 1 running");
            System.Threading.Thread.Sleep(1000);
            Console.WriteLine("Task 1 completed");
        });

        // 创建带返回值的任务
        Task<int> task2 = Task.Run(() =>
        {
            Console.WriteLine("Task 2 running");
            return 42;
        });

        // 使用 Task.Factory 创建任务
        Task<string> task3 = Task.Factory.StartNew(() =>
        {
            Console.WriteLine("Task 3 running");
            return "Hello from Task 3";
        });

        // 等待任务完成
        await task1;
        int result2 = await task2;
        string result3 = await task3;

        Console.WriteLine($"Task 2 result: {result2}");
        Console.WriteLine($"Task 3 result: {result3}");
    }
}

高级异步模式

多个等待

csharp
public class MultipleAwaits
{
    // 顺序执行
    public async Task SequentialExecution()
    {
        Console.WriteLine("Starting sequential execution...");

        string result1 = await GetDataAsync("source1");
        Console.WriteLine($"Got: {result1}");

        string result2 = await GetDataAsync("source2");
        Console.WriteLine($"Got: {result2}");

        string result3 = await GetDataAsync("source3");
        Console.WriteLine($"Got: {result3}");

        Console.WriteLine("Sequential execution complete");
    }

    // 并发执行
    public async Task ConcurrentExecution()
    {
        Console.WriteLine("Starting concurrent execution...");

        Task<string> task1 = GetDataAsync("source1");
        Task<string> task2 = GetDataAsync("source2");
        Task<string> task3 = GetDataAsync("source3");

        string[] results = await Task.WhenAll(task1, task2, task3);

        Console.WriteLine($"Got: {results[0]}");
        Console.WriteLine($"Got: {results[1]}");
        Console.WriteLine($"Got: {results[2]}");

        Console.WriteLine("Concurrent execution complete");
    }

    // 等待任意任务完成
    public async Task WaitForAny()
    {
        Task<string> task1 = GetDataAsync("fast");
        Task<string> task2 = GetDataAsync("slow");
        Task<string> task3 = GetDataAsync("medium");

        Task<string> firstCompleted = await Task.WhenAny(task1, task2, task3);
        string result = await firstCompleted;

        Console.WriteLine($"First completed: {result}");
    }

    private async Task<string> GetDataAsync(string source)
    {
        int delay = source switch
        {
            "fast" => 1000,
            "medium" => 2000,
            "slow" => 3000,
            _ => 1500
        };

        await Task.Delay(delay);
        return $"Data from {source}";
    }
}

异步方法中的异常处理

csharp
public class AsyncExceptionHandling
{
    public async Task HandleExceptions()
    {
        try
        {
            await MethodThatThrowsAsync();
        }
        catch (InvalidOperationException ex)
        {
            Console.WriteLine($"Invalid operation: {ex.Message}");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"General exception: {ex.Message}");
        }
    }

    public async Task HandleMultipleExceptions()
    {
        Task task1 = MethodThatThrowsAsync("Task 1");
        Task task2 = MethodThatThrowsAsync("Task 2");
        Task task3 = SuccessfulMethodAsync("Task 3");

        try
        {
            await Task.WhenAll(task1, task2, task3);
        }
        catch (Exception ex)
        {
            // 只获取第一个异常
            Console.WriteLine($"Exception in WhenAll: {ex.Message}");

            // 要获取所有异常:
            if (ex is AggregateException ae)
            {
                foreach (var innerEx in ae.InnerExceptions)
                {
                    Console.WriteLine($"Inner exception: {innerEx.Message}");
                }
            }
        }
    }

    private async Task MethodThatThrowsAsync(string taskName)
    {
        await Task.Delay(1000);
        throw new InvalidOperationException($"Error in {taskName}");
    }

    private async Task SuccessfulMethodAsync(string taskName)
    {
        await Task.Delay(1500);
        Console.WriteLine($"{taskName} completed successfully");
    }
}

取消支持

csharp
public class AsyncCancellation
{
    public async Task DemonstrateCancellation()
    {
        using CancellationTokenSource cts = new CancellationTokenSource();
        CancellationToken token = cts.Token;

        // 启动长时间运行的操作
        Task longRunningTask = LongRunningOperationAsync(token);

        // 3 秒后取消
        Task.Delay(3000).ContinueWith(_ => cts.Cancel());

        try
        {
            await longRunningTask;
            Console.WriteLine("Operation completed successfully");
        }
        catch (OperationCanceledException)
        {
            Console.WriteLine("Operation was cancelled");
        }
    }

    public async Task LongRunningOperationAsync(CancellationToken token)
    {
        for (int i = 0; i < 10; i++)
        {
            token.ThrowIfCancellationRequested();

            Console.WriteLine($"Processing step {i + 1}/10");
            await Task.Delay(1000, token);
        }

        Console.WriteLine("Long-running operation completed");
    }

    public async Task CancellationWithTimeout()
    {
        using CancellationTokenSource cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));

        try
        {
            await LongRunningOperationAsync(cts.Token);
        }
        catch (OperationCanceledException)
        {
            Console.WriteLine("Operation timed out after 5 seconds");
        }
    }
}

异步流 (C# 8.0+)

IAsyncEnumerable

csharp
public class AsyncStreams
{
    // 返回 IAsyncEnumerable<T> 的方法
    public async IAsyncEnumerable<int> GenerateNumbersAsync(int count)
    {
        for (int i = 1; i <= count; i++)
        {
            await Task.Delay(500); // 模拟异步工作
            yield return i;
        }
    }

    // 消费 IAsyncEnumerable<T> 的方法
    public async Task ConsumeAsyncStream()
    {
        await foreach (int number in GenerateNumbersAsync(10))
        {
            Console.WriteLine($"Received: {number}");
        }
    }

    // 实际示例:从数据库读取数据
    public async IAsyncEnumerable<Customer> GetCustomersAsync()
    {
        const int batchSize = 100;
        int offset = 0;

        while (true)
        {
            List<Customer> batch = await GetCustomerBatchAsync(offset, batchSize);

            if (batch.Count == 0)
                yield break;

            foreach (Customer customer in batch)
            {
                yield return customer;
            }

            offset += batchSize;
        }
    }

    private async Task<List<Customer>> GetCustomerBatchAsync(int offset, int size)
    {
        // 模拟数据库查询
        await Task.Delay(100);

        return Enumerable.Range(offset, Math.Min(size, 10))
            .Select(i => new Customer { Id = i, Name = $"Customer {i}" })
            .ToList();
    }
}

public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; }
}

实际示例

异步文件操作

csharp
public class AsyncFileOperations
{
    public async Task ProcessFilesAsync(string[] filePaths)
    {
        List<Task<string>> readTasks = new List<Task<string>>();

        // 并发启动所有文件读取
        foreach (string filePath in filePaths)
        {
            Task<string> readTask = ReadFileAsync(filePath);
            readTasks.Add(readTask);
        }

        // 等待所有文件读取完成
        string[] contents = await Task.WhenAll(readTasks);

        // 处理所有内容
        for (int i = 0; i < contents.Length; i++)
        {
            Console.WriteLine($"File {filePaths[i]}: {contents[i].Length} characters");
        }
    }

    private async Task<string> ReadFileAsync(string filePath)
    {
        using StreamReader reader = new StreamReader(filePath);
        return await reader.ReadToEndAsync();
    }

    public async Task WriteFilesAsync(Dictionary<string, string> fileContents)
    {
        List<Task> writeTasks = new List<Task>();

        foreach (var kvp in fileContents)
        {
            Task writeTask = WriteFileAsync(kvp.Key, kvp.Value);
            writeTasks.Add(writeTask);
        }

        await Task.WhenAll(writeTasks);
    }

    private async Task WriteFileAsync(string filePath, string content)
    {
        using StreamWriter writer = new StreamWriter(filePath);
        await writer.WriteAsync(content);
    }
}

异步 HTTP 操作

csharp
using System.Net.Http;

public class AsyncHttpOperations
{
    private readonly HttpClient _httpClient;

    public AsyncHttpOperations()
    {
        _httpClient = new HttpClient();
    }

    public async Task<string> DownloadContentAsync(string url)
    {
        try
        {
            HttpResponseMessage response = await _httpClient.GetAsync(url);
            response.EnsureSuccessStatusCode();

            return await response.Content.ReadAsStringAsync();
        }
        catch (HttpRequestException ex)
        {
            Console.WriteLine($"HTTP request failed: {ex.Message}");
            return string.Empty;
        }
    }

    public async Task<List<string>> DownloadMultipleUrlsAsync(string[] urls)
    {
        List<Task<string>> downloadTasks = new List<Task<string>>();

        foreach (string url in urls)
        {
            Task<string> downloadTask = DownloadContentAsync(url);
            downloadTasks.Add(downloadTask);
        }

        string[] results = await Task.WhenAll(downloadTasks);
        return results.ToList();
    }

    public async IAsyncEnumerable<string> StreamUrlsAsync(string[] urls)
    {
        foreach (string url in urls)
        {
            string content = await DownloadContentAsync(url);
            yield return content;
        }
    }
}

异步数据库操作

csharp
public class AsyncDatabaseOperations
{
    public async Task<List<Product>> GetProductsAsync()
    {
        // 模拟数据库查询
        await Task.Delay(100);

        return new List<Product>
        {
            new Product { Id = 1, Name = "Laptop", Price = 999.99m },
            new Product { Id = 2, Name = "Mouse", Price = 29.99m },
            new Product { Id = 3, Name = "Keyboard", Price = 79.99m }
        };
    }

    public async Task<Product> GetProductAsync(int id)
    {
        var products = await GetProductsAsync();
        return products.FirstOrDefault(p => p.Id == id);
    }

    public async Task<bool> SaveProductAsync(Product product)
    {
        // 模拟数据库保存
        await Task.Delay(200);
        Console.WriteLine($"Product {product.Name} saved successfully");
        return true;
    }

    public async Task<List<Product>> SearchProductsAsync(string searchTerm)
    {
        var products = await GetProductsAsync();
        return products.Where(p =>
            p.Name.Contains(searchTerm, StringComparison.OrdinalIgnoreCase))
            .ToList();
    }
}

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
}

最佳实践

Async/Await 指南

csharp
// 好:一路使用 async
public async Task<string> GetDataAsync()
{
    var data = await FetchFromDatabaseAsync();
    return ProcessData(data);
}

// 不好:混合同步和异步
public string GetDataBad()
{
    var data = FetchFromDatabaseAsync().Result; // 阻塞!
    return ProcessData(data);
}

// 好:在库代码中使用 ConfigureAwait
public async Task<string> GetDataAsync()
{
    var data = await FetchFromDatabaseAsync().ConfigureAwait(false);
    return ProcessData(data);
}

异常处理最佳实践

csharp
// 好:正确处理异常
public async Task ProcessDataAsync()
{
    try
    {
        await SomeOperationAsync();
    }
    catch (SpecificException ex)
    {
        // 处理特定异常
        LogError(ex);
    }
    catch (Exception ex)
    {
        // 处理一般异常
        LogError(ex);
        throw; // 如果需要,重新抛出
    }
}

// 好:处理 Task.WhenAll 异常
public async Task ProcessMultipleOperationsAsync()
{
    var tasks = new[] { Op1Async(), Op2Async(), Op3Async() };

    try
    {
        await Task.WhenAll(tasks);
    }
    catch (Exception ex)
    {
        // 处理第一个异常
        LogError(ex);
    }

    // 或者处理所有异常
    foreach (var task in tasks)
    {
        if (task.IsFaulted)
        {
            LogError(task.Exception);
        }
    }
}

性能考虑

csharp
// 好:对频繁完成的操作使用 ValueTask
public ValueTask<int> GetValueAsync()
{
    if (_cachedValue.HasValue)
        return new ValueTask<int>(_cachedValue.Value);

    return new ValueTask<int>(LoadValueAsync());
}

// 好:除了事件处理程序外避免使用 async void
public event EventHandler SomethingHappened;

protected virtual void OnSomethingHappened()
{
    SomethingHappened?.Invoke(this, EventArgs.Empty);
}

// 好:在库代码中使用 ConfigureAwait(false)
public async Task<string> GetDataAsync()
{
    var result = await SomeOperationAsync().ConfigureAwait(false);
    return result;
}

总结

在本章中,您学习了:

  • 基本的 async/await 概念和语法
  • 基于任务的异步模式 (TAP)
  • 多个异步操作和协调
  • 异步方法中的异常处理
  • 使用 CancellationToken 的取消支持
  • 使用 IAsyncEnumerable 的异步流
  • 文件、HTTP 和数据库的实际示例
  • 最佳实践和性能考虑

异步编程对于构建能够高效处理多个操作的响应式应用程序至关重要。掌握 async/await 将帮助您编写更好、更可扩展的 C# 应用程序。