任务上的C#异步/等待进度事件<>;对象

本文关键字:事件 lt gt 对象 异步 等待 任务 | 更新日期: 2023-09-27 18:04:04

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

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

有没有一种方法可以让我做类似于以下的事情?:

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

任务上的C#异步/等待进度事件<>;对象

推荐的方法在基于任务的异步模式文档中进行了描述,该文档为每个异步方法提供了自己的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时重用同一个arguments对象
  3. Progress<T>类型将在构造时捕获当前上下文(例如UI上下文(,并在该上下文中引发其ProgressChanged事件。因此,在调用Report之前,您不必担心封送回UI线程

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

Windows运行时(WinRT(APIIAsyncOperationWithProgress<TResult, TProgress>IAsyncActionWithProgress<TProgress>类型中内置了进度指示器。。。因此,如果你真的在为WinRT写作,这些都值得一看——但也要阅读下面的评论。

我不得不从几篇文章中拼凑出这个答案,因为我正试图找出如何使它适用于不那么琐碎的代码(即事件通知更改(。

让我们假设你有一个同步项目处理器,它会宣布它将要开始工作的项目编号。对于我的例子,我只是要操作处理按钮的内容,但你可以很容易地更新进度条等。

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";
}

关键部分是关闭ItemProcessed订阅中的Progress<>变量。这允许Just works ™的所有内容。

当使用Task.Run lambda时,我在其中使用了InvokeAction来更新ProgressBar控件。这可能不是最好的方式,但它在不需要重组的情况下在紧要关头发挥了作用。

   Invoke(new Action(() =>
               {
                   LogProgress();
               }));

这将把它带到…

        private void LogProgress()
        {       
          progressBar1.Value = Convert.ToInt32((100 * (1.0 * LinesRead / TotalLinesToRead)));
        }