C#async / await Task 对象上的Progress事件

我完全不async C#5的新async / await关键字,我对实现进度事件的最佳方式感兴趣。

现在我更喜欢它,如果一个Progress事件在Task本身上。 我知道我可以把事件放在包含异步方法的类中,并在事件处理程序中传递某种状态对象,但对我而言,这似乎更像是一种解决方法而不是解决方案。 我可能还需要不同的任务来触发不同对象中的事件处理程序,这听起来很混乱。

有没有办法可以做类似以下的事情?:

 var task = scanner.PerformScanAsync(); task.ProgressUpdate += scanner_ProgressUpdate; return await task; 

简而言之, Task 支持进度。 但是,使用IProgress接口已经有了传统的方法。 基于任务的异步模式基本上建议重载异步方法(在有意义的地方)允许客户端传入IProgess实现。 然后,您的异步方法将通过该方法报告进度。

Windows运行时(WinRT)API 确实内置了进度指示器,在IAsyncOperationWithProgressIAsyncActionWithProgress类型中…所以,如果您实际上正在为WinRT编写,那些值得研究 – 但请阅读以下评论也是如此。

基于任务的异步模式文档中描述了推荐的方法,该文档为每个异步方法提供了自己的IProgress

 public async Task PerformScanAsync(IProgress progress) { ... if (progress != null) progress.Report(new MyScanProgress(...)); } 

用法:

 var progress = new Progress(); progress.ProgressChanged += ... PerformScanAsync(progress); 

笔记:

  1. 按照惯例,如果调用者不需要进度报告,则progress参数可以为null ,因此请务必在async方法中检查这一点。
  2. 进度报告本身是异步的,因此每次调用时都应该创建一个新的参数实例(更好的是,只为事件args使用不可变类型)。 您不应该变异,然后重复使用相同的参数对象来进行多次调用Progress
  3. Progress类型将捕获构造中的当前上下文(例如,UI上下文),并将在该上下文中引发其ProgressChanged事件。 因此,在调用Report之前,您不必担心编组返回UI线程。

我不得不将这个答案从几个post中拼凑起来,因为我试图弄清楚如何使这项工作适用于不那么简单的代码(即事件通知更改)。

假设您有一个同步项目处理器,它将宣布它即将开始工作的项目编号。 对于我的示例,我只是操纵Process按钮的内容,但您可以轻松更新进度条等。

 private async void BtnProcess_Click(object sender, RoutedEventArgs e) { BtnProcess.IsEnabled = false; //prevent successive clicks var p = new Progress(); p.ProgressChanged += (senderOfProgressChanged, nextItem) => { BtnProcess.Content = "Processing page " + nextItem; }; var result = await Task.Run(() => { var processor = new SynchronousProcessor(); processor.ItemProcessed += (senderOfItemProcessed , e1) => ((IProgress) p).Report(e1.NextItem); var done = processor.WorkItWorkItRealGood(); return done ; }); BtnProcess.IsEnabled = true; BtnProcess.Content = "Process"; } 

关键是要关闭ItemProcessed订阅中的Progress<>变量。 这样就可以实现Just works ™