42

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

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

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

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

4 回答 4

61

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

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

用法:

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

笔记:

  1. 按照惯例,该progress参数可能是null调用者不需要进度报告的情况,因此请务必在您的async方法中检查这一点。
  2. 进度报告本身是异步的,因此您应该在每次调用时创建一个新的参数实例(更好的是,只需为事件参数使用不可变类型)。您不应该Progress.
  3. Progress<T>类型将在构造时捕获当前上下文(例如,UI 上下文),并将ProgressChanged在该上下文中引发其事件。因此,您不必担心在调用Report.
于 2013-03-14T11:39:22.020 回答
49

简单地说,Task 支持进步。然而,已经有一种传统的方式来做到这一点,即使用IProgress<T>界面。基于任务的异步模式基本上建议重载异步方法(在有意义的地方)以允许客户端传入IProgress<T>实现。然后,您的 async 方法将通过它报告进度。

Windows 运行时 (WinRT) API确实IAsyncOperationWithProgress<TResult, TProgress>在和类型中内置了进度指示器IAsyncActionWithProgress<TProgress>...因此,如果您实际上是为 WinRT 编写代码,那么这些都值得研究 - 但也请阅读下面的评论。

于 2013-03-14T11:39:05.217 回答
10
于 2014-11-06T16:22:38.633 回答
0

When using a Task.Run lambda I have used an Invoke Action inside of this to update a ProgressBar control. This may not be the best way but it worked in a pinch without having to restructure anything.

   Invoke(new Action(() =>

               {
                   LogProgress();
               }));

Which takes it to...

        private void LogProgress()
        {       
          progressBar1.Value = Convert.ToInt32((100 * (1.0 * LinesRead / TotalLinesToRead)));
        }
于 2020-11-27T18:55:14.630 回答