代码之家  ›  专栏  ›  技术社区  ›  Connell

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

  •  46
  • Connell  · 技术社区  · 13 年前

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

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

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

    var task = scanner.PerformScanAsync();
    task.ProgressUpdate += scanner_ProgressUpdate;
    return await task;
    
    4 回复  |  直到 13 年前
        1
  •  67
  •   Stephen Cleary    13 年前

    建议的方法在 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. 进度报告本身是异步的,所以每次调用时都应该创建一个参数的新实例(更好的是,只需为事件参数使用不可变类型)。你应该 不 变异,然后将同一个arguments对象用于多次调用 Progress .
    3. 这个 Progress<T> 类型将在构造时捕获当前上下文(例如,UI上下文),并将引发其 ProgressChanged 事件。因此,在调用之前,您不必担心封送回UI线程 Report .
        2
  •  49
  •   Marc.2377    4 年前

    简单地说, Task 不 支持进度。然而,已经有一种传统的方法可以做到这一点,使用 IProgress<T> 界面这个 Task-based Asynchronous Pattern 基本上建议重载异步方法(在有意义的地方),以允许客户端传入 IP进程<T> 实施然后,您的异步方法将通过它报告进度。

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

        3
  •  11
  •   Chris Marisic    11 年前

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

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

    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 ™ .

        4
  •  0
  •   Ian Fafard    5 年前

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

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

    这就把它带到了。。。

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