异步编程时经常需要向用户显示任务的进度并提供取消任务的功能,本篇就讲解在使用 async 时如何做到这两点。
本篇基本就是 Async in 4.5: Enabling Progress and Cancellation in Async APIs 的一个摘录,英语好的话还是推荐阅读原博文。
.NET 中主要有 3 种异步编程模式,对于新代码最推荐的模式就是基于 Task 的 Task-based asynchronous pattern (TAP) ,关于 TAP 模式的详细内容请参考 Docs。
注:Docs 中把 TAP 模式的实现(Implementing)和使用(Consuming)的示例放到了两个文档中。
汇报进度
通过实现 IProgress
Async 方法内部使用 Report 方法汇报进度:
public async Task<int> UploadPicturesAsync(List<Image> imageList,
IProgress<int> progress)
{
int totalCount = imageList.Count;
int processCount = await Task.Run<int>(() =>
{
int tempCount = 0;
foreach (var image in imageList)
{
//await the processing and uploading logic here
int processed = await UploadAndProcessAsync(image);
if (progress != null)
{
progress.Report((tempCount * 100 / totalCount));
}
tempCount++;
}
return tempCount;
});
return processCount;
}
根据 Report 的进度刷新前台的进度条控件:
private async void Start_Button_Click(object sender, RoutedEventArgs e)
{
int uploads = await UploadPicturesAsync(GenerateTestImages(),
new Progress<int>(percent => progressBar1.Value = percent));
}
取消任务
取消任务用的是 CancellationToken 结构。
CancellationToken 结构:异步方法如果想支持取消,就在异步方法的签名中增加一个 CancellationToken 参数,然后调用者调用异步方法时传入一个 CancellationToken,即调用者和异步方法共享一个 CancellationToken。
最常见的流程是:
- 调用者创建一个 CancellationTokenSource 对象
- 调用者调用支持取消的异步方法,并传入 CancellationTokenSource.Token
- 调用者使用 CancellationTokenSource.Cancel() 来取消异步方法
- 异步方法内确认取消并取消任务,通常使用 CancellationToken.ThrowIfCancellationRequested
修改方法签名以传入 CancellationToken,然后在异步方法中通过 ct.ThrowIfCancellationRequested() 检查任务是否已被取消,若已取消则抛出 OperationCanceledException。
public async Task<int> UploadPicturesAsync(List<Image> imageList,
IProgress<int> progress,
CancellationToken ct)
{
int processCount = await Task.Run<int>(() =>
{
foreach (var image in imageList)
{
//await UploadAndProcessAsync (this is another method in the app)
bool success = await UploadAndProcessAsync(image);
ct.ThrowIfCancellationRequested();
// progress logic here
}
},ct);
return processCount;
}
ThrowIfCancellationRequested() 源码:
public void ThrowIfCancellationRequested()
{
if (!this.IsCancellationRequested)
return;
this.ThrowOperationCanceledException();
}
注:.NET 内置的耗时较长的异步方法基本都有支持 CancellationToken 的重载。如 await Task.Delay(2000, ct)
。
- 调用方法时传入 CancellationTokenSource 的 Token
- 通过调用 Cancel 方法触发取消请求
- 通过捕获 OperationCanceledException 处理任务的取消 ```csharp private CancellationTokenSource _cts;
private async void Start_Button_Click(object sender, RoutedEventArgs e)
{
var progressIndicator = new Progress
private void Cancel_Button_Click(object sender, RoutedEventArgs e) { _cts.Cancel(); } ```