任务上的C#异步/等待进度事件<>;对象
本文关键字:事件 lt gt 对象 异步 等待 任务 | 更新日期: 2023-09-27 18:04:04
我对C#5的新async
/await
关键字完全陌生,我对实现进度事件的最佳方式感兴趣。
现在我更希望Progress
事件在Task<>
本身上。我知道我可以把事件放在包含异步方法的类中,并在事件处理程序中传递某种状态对象,但对我来说,这似乎更像是一种变通方法,而不是解决方案。我可能还希望不同的任务在不同的对象中启动事件处理程序,这听起来很混乱。
有没有一种方法可以让我做类似于以下的事情?:
var task = scanner.PerformScanAsync();
task.ProgressUpdate += scanner_ProgressUpdate;
return await task;
推荐的方法在基于任务的异步模式文档中进行了描述,该文档为每个异步方法提供了自己的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);
注:
- 按照惯例,如果调用方不需要进度报告,那么
progress
参数可能是null
,所以一定要在async
方法中检查这一点 - 进度报告本身是异步的,所以每次调用时都应该创建一个参数的新实例(更好的是,只需为事件参数使用不可变类型(。您应该而不是突变,然后在多次调用
Progress
时重用同一个arguments对象 Progress<T>
类型将在构造时捕获当前上下文(例如UI上下文(,并在该上下文中引发其ProgressChanged
事件。因此,在调用Report
之前,您不必担心封送回UI线程
简单地说,Task
不支持进度。然而,已经有一种传统的方法可以做到这一点,即使用IProgress<T>
接口。基于任务的异步模式基本上建议重载异步方法(在有意义的地方(,以允许客户端传入IProgress<T>
实现。然后,您的异步方法将通过它报告进度。
Windows运行时(WinRT(API在IAsyncOperationWithProgress<TResult, TProgress>
和IAsyncActionWithProgress<TProgress>
类型中内置了进度指示器。。。因此,如果你真的在为WinRT写作,这些都值得一看——但也要阅读下面的评论。
我不得不从几篇文章中拼凑出这个答案,因为我正试图找出如何使它适用于不那么琐碎的代码(即事件通知更改(。
让我们假设你有一个同步项目处理器,它会宣布它将要开始工作的项目编号。对于我的例子,我只是要操作处理按钮的内容,但你可以很容易地更新进度条等。
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<>
变量。这允许Just works ™
的所有内容。
当使用Task.Run lambda时,我在其中使用了InvokeAction来更新ProgressBar控件。这可能不是最好的方式,但它在不需要重组的情况下在紧要关头发挥了作用。
Invoke(new Action(() =>
{
LogProgress();
}));
这将把它带到…
private void LogProgress()
{
progressBar1.Value = Convert.ToInt32((100 * (1.0 * LinesRead / TotalLinesToRead)));
}