C# 异步编程

19.1 异步编程概述

异步编程允许程序在等待耗时操作时继续执行其他任务。

同步问题

1
2
3
4
5
6
7
// 同步方式 - 会阻塞线程
void DownloadData()
{
Console.WriteLine("开始下载...");
Thread.Sleep(3000); // 阻塞 3 秒
Console.WriteLine("下载完成");
}

异步方式

1
2
3
4
5
6
7
// 异步方式 - 不阻塞线程
async Task DownloadDataAsync()
{
Console.WriteLine("开始下载...");
await Task.Delay(3000); // 不阻塞
Console.WriteLine("下载完成");
}

19.2 async 与 await

基本用法

1
2
3
4
5
6
7
8
9
10
11
12
13
// 定义异步方法
public async Task<int> CalculateAsync()
{
await Task.Delay(1000); // 模拟异步操作
return 42;
}

// 调用异步方法
public async void CallAsync()
{
int result = await CalculateAsync();
Console.WriteLine(result); // 42
}

异步方法签名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 无返回值
public async Task DoSomethingAsync()
{
await Task.Delay(1000);
}

// 有返回值
public async Task<int> GetResultAsync()
{
await Task.Delay(1000);
return 100;
}

// 事件处理(可以返回 void)
public async void Button_Click(object sender, EventArgs e)
{
await LoadDataAsync();
}

19.3 Task 与 Task

创建 Task

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Task.Run 在线程池执行
Task task = Task.Run(() =>
{
Console.WriteLine("在后台线程执行");
});

// Task<T> 带返回值
Task<int> task = Task.Run(() =>
{
Thread.Sleep(1000);
return 42;
});

int result = await task;

Task 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 等待所有任务完成
Task task1 = Task.Delay(1000);
Task task2 = Task.Delay(2000);

await Task.WhenAll(task1, task2);

// 等待任意任务完成
Task<int> fastTask = Task.Run(() => 1);
Task<int> slowTask = Task.Run(async () =>
{
await Task.Delay(1000);
return 2;
});

Task<int> completed = await Task.WhenAny(fastTask, slowTask);

// 延迟
await Task.Delay(500);

19.4 ConfigureAwait

1
2
3
4
5
6
7
8
9
10
11
// UI 应用中保持上下文
public async Task LoadDataAsync()
{
// 默认行为:捕获上下文回到原线程
var data = await GetDataAsync();
textBox.Text = data; // 需要 UI 线程

// 不捕获上下文(性能更好)
var result = await ComputeAsync().ConfigureAwait(false);
// 不保证回到原线程
}

19.5 异常处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public async Task ProcessAsync()
{
try
{
await DoWorkAsync();
}
catch (HttpRequestException ex)
{
Console.WriteLine($"网络错误: {ex.Message}");
}
catch (TaskCanceledException)
{
Console.WriteLine("任务被取消");
}
catch (Exception ex)
{
Console.WriteLine($"错误: {ex.Message}");
}
}

// 多个任务异常
public async Task HandleMultipleAsync()
{
Task task1 = Task.Run(() => throw new Exception("错误 1"));
Task task2 = Task.Run(() => throw new Exception("错误 2"));

try
{
await Task.WhenAll(task1, task2);
}
catch (AggregateException ae)
{
foreach (var ex in ae.InnerExceptions)
{
Console.WriteLine(ex.Message);
}
}
}

19.6 取消操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public async Task ProcessWithCancellationAsync(CancellationToken ct)
{
for (int i = 0; i < 10; i++)
{
ct.ThrowIfCancellationRequested(); // 检查取消

await Task.Delay(1000, ct); // 支持取消的延迟
Console.WriteLine($"步骤 {i + 1}");
}
}

// 使用
public async void StartProcess()
{
using var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(3)); // 3 秒后取消

try
{
await ProcessWithCancellationAsync(cts.Token);
}
catch (OperationCanceledException)
{
Console.WriteLine("操作已取消");
}
}

19.7 并行处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 并行执行多个独立任务
public async Task ProcessImagesAsync(string[] urls)
{
var tasks = urls.Select(url => DownloadImageAsync(url));

await Task.WhenAll(tasks);
}

// 限制并发
public async Task ProcessWithLimitAsync(string[] items)
{
var semaphore = new SemaphoreSlim(3); // 最多 3 个并发

var tasks = items.Select(async item =>
{
await semaphore.WaitAsync();
try
{
await ProcessItemAsync(item);
}
finally
{
semaphore.Release();
}
});

await Task.WhenAll(tasks);
}

19.8 异步最佳实践

1. Async 贯穿始终

1
2
3
4
5
6
7
8
9
10
11
// 不推荐:同步阻塞
public void Process()
{
var result = GetDataAsync().Result; // 可能死锁
}

// 推荐:Async 贯穿
public async Task ProcessAsync()
{
var result = await GetDataAsync();
}

2. 避免 async void

1
2
3
4
5
6
7
8
9
10
11
// 不推荐
public async void Button_Click(object sender, EventArgs e)
{
await LoadDataAsync();
}

// 推荐(除事件处理外)
public async Task LoadDataAsync()
{
await Task.Delay(1000);
}

3. 使用 ValueTask

1
2
3
4
5
6
7
8
9
// 减少分配(适用于常用结果)
public ValueTask<int> GetCachedResultAsync()
{
if (_cached)
{
return new ValueTask<int>(_result); // 同步返回
}
return new ValueTask<int>(LoadResultAsync());
}

19.9 本章小结

  • async 标记异步方法,await 等待异步操作
  • Task 无返回值,Task<T> 带返回值
  • Task.WhenAll 等待所有,Task.WhenAny 等待任意
  • ConfigureAwait(false) 避免上下文切换
  • CancellationToken 支持取消操作
  • 异步异常用 try-catch 捕获
  • 使用 SemaphoreSlim 限制并发

练习题

  1. 编写异步方法下载文件并显示进度
  2. 使用 Task.WhenAll 并行处理多个请求
  3. 实现可取消的异步操作
  4. 以下代码输出什么?
    1
    2
    3
    4
    5
    6
    7
    async Task PrintAsync()
    {
    await Task.Delay(100);
    Console.WriteLine("World");
    }
    Console.Write("Hello ");
    await PrintAsync();
ByteFisher
分享编程技术 · 记录钓鱼乐趣
扫码关注
▸ 扫码关注 ◂
分享: