我对C#5的新async
/ await
关键字完全不熟悉,我对实施进度事件的最佳方式感兴趣。
现在,如果Progress
本身有Task<>
事件,我会更喜欢它。我知道我可以把事件放在包含异步方法的类中,并在事件处理程序中传递某种状态对象,但对我而言,这似乎更像是一种解决方法而不是解决方案。我可能也想要不同的任务来触发不同对象中的事件处理程序,这听起来很混乱。
有没有办法可以做类似以下的事情?:
var task = scanner.PerformScanAsync();
task.ProgressUpdate += scanner_ProgressUpdate;
return await task;
答案 0 :(得分:47)
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);
注意:
progress
参数可能为null
,因此请务必使用async
方法检查此内容。Progress
。Progress<T>
类型将捕获构造中的当前上下文(例如,UI上下文),并将在该上下文中引发其ProgressChanged
事件。因此,在调用Report
之前,您不必担心编组返回UI线程。答案 1 :(得分:42)
简单地说,Task
不支持支持进度。但是,使用IProgress<T>
接口已经有了传统的方法。 Task-based Asynchronous Pattern基本上建议重载异步方法(在有意义的地方)允许客户端传入IProgess<T>
实现。然后,您的异步方法将通过该方法报告进度。
Windows运行时(WinRT)API 确实内置了IAsyncOperationWithProgress<TResult, TProgress>
和IAsyncActionWithProgress<TProgress>
类型的进度指示器...所以如果您实际上正在编写WinRT,值得研究 - 但也请阅读以下评论。
答案 2 :(得分:7)
我不得不将这个答案从几个帖子中拼凑起来,因为我试图弄清楚如何使代码变得不那么简单(即事件通知更改)。
假设您有一个同步项目处理器,它将宣布它即将开始工作的项目编号。对于我的例子,我只是操纵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";
}
关键部分是关闭Progress<>
订阅中的ItemProcessed
变量。这允许所有内容都为Just works ™
。
答案 3 :(得分:0)
在使用Task.Run lambda时,我在其中使用了一个调用动作来更新ProgressBar控件。这可能不是最好的方法,但是它可以在紧急状态下工作,而无需重组任何内容。
Invoke(new Action(() =>
{
LogProgress();
}));
将其带到...
private void LogProgress()
{
progressBar1.Value = Convert.ToInt32((100 * (1.0 * LinesRead / TotalLinesToRead)));
}