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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/15408148/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-10 16:43:17  来源:igfitidea点击:

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

c#async-await

提问by Connell

I'm completely new to C# 5's new async/awaitkeywords and I'm interested in the best way to implement a progress event.

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

Now I'd prefer it if a Progressevent was on the Task<>itself. 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.

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

Is there a way I could do something similar to the following?:

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

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

采纳答案by Jon Skeet

Simply put, Taskdoesn'tsupport progress. However, there's already a conventional way of doing this, using the IProgress<T>interface. The Task-based Asynchronous Patternbasically suggests overloading your async methods (where it makes sense) to allow clients to pass in an IProgess<T>implementation. Your async method would then report progress via that.

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

The Windows Runtime (WinRT) API doeshave 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 编写代码,那么这些内容值得研究 - 但也请阅读下面的评论。

回答by Stephen Cleary

The recommended approach is described in the Task-based Asynchronous Patterndocumentation, 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 progressparameter may be nullif the caller doesn't need progress reports, so be sure to check for this in your asyncmethod.
  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 notmutate and then re-use the same arguments object for multiple calls to Progress.
  3. The Progress<T>type will capture the current context (e.g., UI context) on construction and will raise its ProgressChangedevent in that context. So you don't have to worry about marshaling back to the UI thread before calling Report.
  1. 按照惯例,progress参数可能是null调用者不需要进度报告,所以一定要在你的async方法中检查这个。
  2. 进度报告本身是异步的,因此每次调用时都应该创建一个新的参数实例(更好的是,只需为事件参数使用不可变类型)。你应该不会发生变异,然后再使用相同的参数为对象多次调用Progress
  3. Progress<T>类型将在构造时捕获当前上下文(例如,UI 上下文)并将ProgressChanged在该上下文中引发其事件。因此,您不必担心在调用Report.

回答by Chris Marisic

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 ItemProcessedsubscription. This allows everything to Just works ?.

关键部分是关闭订阅Progress<>内部的变量ItemProcessed。这允许一切Just works ?