【问题标题】:C# async/await Progress event on Task<> objectTask<> 对象上的 C# async/await Progress 事件
【发布时间】:2013-03-02 17:39:23
【问题描述】:

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

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

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

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

【问题讨论】:

    标签: c# async-await


    【解决方案1】:

    简单地说,Task 支持进度。但是,已经有一种传统的方法可以做到这一点,使用IProgress&lt;T&gt; 接口。 Task-based Asynchronous Pattern 基本上建议重载您的异步方法(在有意义的地方)以允许客户端传入 IProgress&lt;T&gt; 实现。然后,您的 async 方法将通过它报告进度。

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

    【讨论】:

    • 对于 WinRT,使用IProgress&lt;T&gt;(遵循 C# 约定)编写普通的 async 方法会容易得多,然后使用 AsyncInfo.Run 将其包装成 IAsyncOperationWithProgress/@987654334 @ 而不是直接实现这些接口。
    • @StephenCleary:这取决于你是考虑调用者还是实现。如果您试图公开功能供其他人调用,我会认为 WinRT“本机”类型会更合适。显然两者之间存在各种转换器。
    • 在 C# 中遵循 C# 约定更容易,并在边界处使用包装器 (AsTask/AsyncInfo)。因此,在 C# 中实现的 WinRT 组件将返回 WinRT 接口类型,但会使用包装在 AsyncInfo 中的常规 async 来实现它。
    • @StephenCleary:很公平,我相信你的话 :)
    【解决方案2】:

    Task-based Asynchronous Pattern 文档中描述了推荐的方法,该文档为每个异步方法提供了自己的IProgress&lt;T&gt;

    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&lt;T&gt; 类型将在构造时捕获当前上下文(例如 UI 上下文),并将在该上下文中引发其 ProgressChanged 事件。因此,在调用 Report 之前,您不必担心会封送回 UI 线程。

    【讨论】:

    • 只是为了让我理解并可能在将来帮助其他人......您对第 2 点的推理是,如果在 Report 方法完成之前对传递给 Progress.Report() 的可变对象进行了更改那么它会导致原始值不被报告?
    • 差不多。我的意思是当Report 方法完成(并返回)时,实际的报告还没有发生。如果您的T 是可变的,并且您在将其传递给Report 后对其进行了更改,那么您就有了竞争条件。实际报告可能会看到旧值、新值或混合(即某些字段的旧值和其他字段的新值)。
    • 是的,现在更有意义了,谢谢。我阅读了您的一些博客等,其中您提到了比赛条件,但直到我阅读了这个问题后才立即显现出来。谢谢
    • 是否不能保证 ProgressChanged 委托按顺序获得 Report() 的内容?根据我的理解,即使传递的对象是不可变的,如果我用我收到的最新 T 更新接口,我有机会更新它以显示更多过时的信息,因为我在之后收到了一个较旧的进度对象一个较新的。
    • @deed02392:从技术上讲,没有订购要求。但是,我知道的每个实现都保留了顺序。
    【解决方案3】:

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

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

    其中的关键部分是关闭ItemProcessed 订阅中的Progress&lt;&gt; 变量。这让一切都可以Just works ™

    【讨论】:

    • 不确定“SynchronousProcessor”从何而来。
    • @FengJiang 那是你需要同步处理的代码,不管是什么。它说明了如何使用 your 处理器的 ItemProcessed 事件绑定到 ProgressChanged 事件以允许异步 UI 更新。
    【解决方案4】:

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

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

    它需要...

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

    【讨论】:

    • 这将在主 GUI 线程上更新您的progressBar,因此它会使表单无响应,尤其是在循环太大的情况下。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-10-08
    • 2012-08-27
    • 2012-03-02
    • 2014-09-25
    • 2015-02-11
    • 2013-09-04
    相关资源
    最近更新 更多