简体   繁体   English

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

[英]C# async/await Progress event on Task<> object

I'm completely new to C# 5's new async / await keywords and I'm interested in the best way to implement a progress event.我对 C# 5 的新async / await关键字完全async ,我对实现进度事件的最佳方式很感兴趣。

Now I'd prefer it if a Progress event was on the Task<> itself.现在,如果Progress事件发生在Task<>本身上,我更喜欢它。 I know I could just put the event in the class that contains the asynchronous method and pass some sort of state object in the event handler, but to me that seems like more of a workaround than a solution.我知道我可以将事件放在包含异步方法的类中,并在事件处理程序中传递某种状态对象,但在我看来,这更像是一种解决方法而不是解决方案。 I might also want different tasks to fire off event handlers in different objects, which sounds messy this way.我可能还希望不同的任务触发不同对象中的事件处理程序,这样听起来很混乱。

Is there a way I could do something similar to the following?:有没有办法可以做类似于以下的事情?:

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

The recommended approach is described in the Task-based Asynchronous Pattern documentation, which gives each asynchronous method its own IProgress<T> :推荐的方法在基于任务的异步模式文档中有描述,它为每个异步方法提供了自己的IProgress<T>

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

Usage:用法:

var progress = new Progress<MyScanProgress>();
progress.ProgressChanged += ...
PerformScanAsync(progress);

Notes:笔记:

  1. By convention, the progress parameter may be null if the caller doesn't need progress reports, so be sure to check for this in your async method.按照惯例,如果调用者不需要进度报告, progress参数可能为null ,因此请务必在async方法中检查这一点。
  2. Progress reporting is itself asynchronous, so you should create a new instance of your arguments each time you call (even better, just use immutable types for your event args).进度报告本身是异步的,因此每次调用时都应该创建一个新的参数实例(更好的是,只需为事件参数使用不可变类型)。 You should not mutate and then re-use the same arguments object for multiple calls to Progress .应该发生变异,然后再使用相同的参数为对象多次调用Progress
  3. The Progress<T> type will capture the current context (eg, UI context) on construction and will raise its ProgressChanged event in that context. Progress<T>类型将在构造时捕获当前上下文(例如 UI 上下文),并将在该上下文中引发其ProgressChanged事件。 So you don't have to worry about marshaling back to the UI thread before calling Report .因此,您不必担心在调用Report之前封送回 UI 线程。

Simply put, Task doesn't support progress.简单地说, Task支持进度。 However, there's already a conventional way of doing this, using the IProgress<T> interface.但是,已经有一种使用IProgress<T>接口的传统方法可以做到这一点。 The Task-based Asynchronous Pattern basically suggests overloading your async methods (where it makes sense) to allow clients to pass in an IProgess<T> implementation.基于任务的异步模式基本上建议重载异步方法(在有意义的地方)以允许客户端传入IProgess<T>实现。 Your async method would then report progress via that.然后,您的异步方法将通过该方法报告进度。

The Windows Runtime (WinRT) API does have progress indicators built-in, in the IAsyncOperationWithProgress<TResult, TProgress> and IAsyncActionWithProgress<TProgress> types... so if you're actually writing for WinRT, those are worth looking into - but read the comments below as well. Windows 运行时 (WinRT) API确实有内置的进度指示器,在IAsyncOperationWithProgress<TResult, TProgress>IAsyncActionWithProgress<TProgress>类型中......所以如果你实际上是为 WinRT 编写,这些值得研究 - 但请阅读下面的评论也是如此。

I had to piece together this answer from several posts as I was trying to figure out how to make this work for code that is less trivial (ie events notify changes).我不得不从几篇文章中拼凑出这个答案,因为我试图弄清楚如何使这个工作适用于不太重要的代码(即事件通知更改)。

Let's assume you have a synchronous item processor that will announce the item number it is about to start work on.假设您有一个同步项目处理器,它将宣布即将开始处理的项目编号。 For my example I am just going to manipulate the content of the Process button, but you can easily update a progress bar etc.在我的示例中,我将只操作 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";
}

The key part to this is closing over the Progress<> variable inside ItemProcessed subscription.关键部分是关闭ItemProcessed订阅中的Progress<>变量。 This allows everything to Just works ™ .这使得一切都可以Just works ™

When using a Task.Run lambda I have used an Invoke Action inside of this to update a ProgressBar control.使用 Task.Run lambda 时,我在其中使用了 Invoke Action 来更新 ProgressBar 控件。 This may not be the best way but it worked in a pinch without having to restructure anything.这可能不是最好的方法,但它可以在紧要关头工作,而无需重组任何东西。

   Invoke(new Action(() =>

               {
                   LogProgress();
               }));

Which takes it to...这需要它...

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM