这个实战案例非常经典,它完美地结合了 Action<T>(进度回调委托) 、Task(异步操作载体) 和 CancellationTokenSource(超时与取消机制)。
我们将构建一个模拟下载器,它可以一边下载一边通过委托汇报进度,同时如果超过规定时间则自动停止。
1. 核心设计思路
- 进度汇报: 使用
Action<int>委托,由下载逻辑在循环中调用。 - 超时处理: 使用
CancellationTokenSource配合CancelAfter方法,这是现代 C# 处理超时的标准做法。 - 异步封装: 使用
Task.Run将下载逻辑放入后台,避免阻塞 UI。
2. 实战代码实现
csharp
using System;
using System.Threading;
using System.Threading.Tasks;
public class FileDownloader
{
// 异步下载方法
// progressCallback: 一个接收 int (0-100) 的委托,用于更新进度
// ct: 用于监听超时或取消操作的令牌
public async Task DownloadFileAsync(string url, Action<int> progressCallback, CancellationToken ct)
{
Console.WriteLine($"开始从 {url} 下载...");
for (int i = 1; i <= 10; i++)
{
// 检查是否已经超时或被取消
ct.ThrowIfCancellationRequested();
// 模拟耗时的网络操作
await Task.Delay(500, ct);
// 计算进度
int progress = i * 10;
// --- 委托的核心应用 ---
// 调用外部传入的逻辑,调用者决定怎么显示进度(打印、进度条等)
progressCallback?.Invoke(progress);
}
Console.WriteLine("\n下载完成!");
}
}
class Program
{
static async Task Main()
{
FileDownloader downloader = new FileDownloader();
// 1. 设置超时时间为 3 秒 (若下载需要 5 秒则会触发超时)
using var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(3));
try
{
// 2. 定义进度回调委托 (使用 Lambda 表达式实现 Action<int>)
Action<int> onProgress = (p) => {
Console.Write($"\r当前进度: {p}% ");
};
// 3. 启动异步任务
await downloader.DownloadFileAsync("https://example.com/bigfile.zip", onProgress, cts.Token);
}
catch (OperationCanceledException)
{
// 如果超时,会抛出此异常
Console.WriteLine("\n[错误]: 下载超时或已被用户取消。");
}
catch (Exception ex)
{
Console.WriteLine($"\n发生未知错误: {ex.Message}");
}
}
}
3. 深度解析:委托与任务的协作
1. 委托作为"解耦器"
在上面的代码中,FileDownloader 类并不知道如何"显示"进度。它只是持有一个 Action<int> 委托。
- 如果是控制台程序 ,委托里写的是
Console.WriteLine。 - 如果是桌面 UI 程序 ,委托里写的可能是更新
ProgressBar控件。
这就实现了业务逻辑(下载)与展示逻辑(进度条)的完美分离。
2. CancellationToken 的本质
CancellationToken 内部其实也依赖于委托。当你调用 CancelAfter 时,它会触发一个内部委托,通知所有持有该 Token 的代码进行"紧急避险"。
3. 执行流对比
| 环节 | 角色 | 作用 |
|---|---|---|
| 启动阶段 | Task |
开启异步执行上下文,不阻塞主线程。 |
| 运行阶段 | Action<int> |
作为双向通信的桥梁,从后台向前端发送实时状态。 |
| 终止阶段 | CancellationToken |
充当安全阀,由委托机制触发中断逻辑。 |
4. 为什么这样写?
- 非阻塞: 使用
await确保了即使在下载,你的 UI 界面也不会卡死。 - 资源可控: 很多人写下载不考虑超时,导致后台线程死锁,使用
CancellationToken是处理异步长任务的最佳实践。 - 高度抽象: 通过
Action委托,你的下载器类可以被复用到任何项目中。