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

Con*_*ell 32 c# async-await

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

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

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

var task = scanner.PerformScanAsync();
task.ProgressUpdate += scanner_ProgressUpdate;
return await task;
Run Code Online (Sandbox Code Playgroud)

Ste*_*ary 48

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

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

用法:

var progress = new Progress<MyScanProgress>();
progress.ProgressChanged += ...
PerformScanAsync(progress);
Run Code Online (Sandbox Code Playgroud)

笔记:

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

  • 几乎.我的意思是当"报告"方法完成(并返回)时,实际报告尚未发生.如果您的`T`是可变的并且在将其传递给`Report`之后更改它,那么您就有竞争条件.实际报告可能会看到旧值,新值或混合(即某些字段的旧值和其他字段的新值). (3认同)
  • 只是我理解并且可能在未来帮助其他人......你对第 2 点的推理是,如果在 Report 方法完成之前对传递给 Progress.Report() 的可变对象进行了更改,那么它将导致原始值不被举报? (2认同)

Jon*_*eet 45

简单地说,Task 支持进展.但是,使用IProgress<T>界面已经有了传统的方法.基于任务的异步模式基本上建议重载异步方法(在有意义的地方)允许客户端传入IProgess<T>实现.然后,您的异步方法将通过该方法报告进度.

Windows运行时(WinRT)API 确实具有内置的进度指示器,在IAsyncOperationWithProgress<TResult, TProgress>IAsyncActionWithProgress<TProgress>类型中...所以如果您实际上正在为WinRT编写,那些值得研究 - 但也请阅读下面的注释.

  • 对于WinRT,使用`IProgress <T>`(遵循C#约定)编写正常的`async`方法要容易得多,然后使用`AsyncInfo.Run`将它包装成`IAsyncOperationWithProgress` /`IAsyncActionWithProgress`而不是直接实现这些接口. (2认同)

Chr*_*sic 9

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

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

private async void BtnProcess_Click(object sender, RoutedEventArgs e)
{       
    BtnProcess.IsEnabled = false; //prevent successive clicks
    var p = new Progress<int>();
    p.ProgressChanged += (senderOfProgressChanged, nextItem) => 
                    { BtnProcess.Content = "Processing page " + nextItem; };

    var result = await Task.Run(() =>
    {
        var processor = new SynchronousProcessor();

        processor.ItemProcessed += (senderOfItemProcessed , e1) => 
                                ((IProgress<int>) p).Report(e1.NextItem);

        var done = processor.WorkItWorkItRealGood();

        return done ;
    });

    BtnProcess.IsEnabled = true;
    BtnProcess.Content = "Process";
}
Run Code Online (Sandbox Code Playgroud)

关键是要关闭Progress<>变量内部ItemProcessed订阅.这允许一切Just works ™.