任务<>上的C#async / await进度事件宾语

时间:2013-03-14 11:36:32

标签: c# async-await

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

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

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

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

4 个答案:

答案 0 :(得分:47)

Task-based Asynchronous Pattern文档中描述了推荐的方法,它为每个异步方法提供了自己的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. 进度报告本身是异步的,因此每次调用时都应该创建一个新的参数实例(更好的是,只为事件args使用不可变类型)。你应该变异,然后重复使用相同的参数对象来多次调用Progress
  3. Progress<T>类型将捕获构造中的当前上下文(例如,UI上下文),并将在该上下文中引发其ProgressChanged事件。因此,在调用Report之前,您不必担心编组返回UI线程。

答案 1 :(得分:42)

简单地说,Task 不支持支持进度。但是,使用IProgress<T>接口已经有了传统的方法。 Task-based Asynchronous Pattern基本上建议重载异步方法(在有意义的地方)允许客户端传入IProgess<T>实现。然后,您的异步方法将通过该方法报告进度。

Windows运行时(WinRT)API 确实内置了IAsyncOperationWithProgress<TResult, TProgress>IAsyncActionWithProgress<TProgress>类型的进度指示器...所以如果您实际上正在编写WinRT,值得研究 - 但也请阅读以下评论。

答案 2 :(得分:7)

我不得不将这个答案从几个帖子中拼凑起来,因为我试图弄清楚如何使代码变得不那么简单(即事件通知更改)。

假设您有一个同步项目处理器,它将宣布它即将开始工作的项目编号。对于我的例子,我只是操纵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";
}

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

答案 3 :(得分:0)

在使用Task.Run lambda时,我在其中使用了一个调用动作来更新ProgressBar控件。这可能不是最好的方法,但是它可以在紧急状态下工作,而无需重组任何内容。

   Invoke(new Action(() =>

               {
                   LogProgress();
               }));

将其带到...

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