从TPL任务向WPF视图报告进度的适当方法是什么?
本文关键字:方法 是什么 任务 TPL WPF 报告 视图 | 更新日期: 2023-09-27 18:01:54
我试图在小"块"或页面从数据库中读取大量行,并向用户报告进度;例如,如果我加载100个"块",报告每个块加载的进度。
我正在使用c# 4.0中的TPL从数据库中读取这些块,然后将完整的结果集交给另一个可以使用它的任务。我觉得TPL给了我比BackgroundWorker更好的控制任务取消和移交等,但是似乎没有一个内置的方式来报告任务的进度。
这是我实现的解决方案,用于向WPF进度条报告进度,我想确保这是合适的,并且没有更好的方法我应该采取。
我首先创建了一个简单的界面来表示变化的进度:
public interface INotifyProgressChanged
{
int Maximum { get; set; }
int Progress { get; set; }
bool IsIndeterminate { get; set; }
}
这些属性可以绑定到WPF视图中的ProgressBar,接口由后台ViewModel实现,它负责初始化数据加载并最终报告总体进度(为本例进行了简化):
public class ContactsViewModel : INotifyProgressChanged
{
private IContactRepository repository;
...
private void LoadContacts()
{
Task.Factory.StartNew(() => this.contactRepository.LoadWithProgress(this))
.ContinueWith(o => this.UseResult(o));
}
}
你会注意到,我将ViewModel传递给存储库方法作为INotifyProgressChanged,这就是我想确保我没有做错的地方。
我在这里的想法是,为了报告进度,实际执行工作的方法(即存储库方法)需要访问INotifyProgressChanged接口,以便报告进度,最终更新视图。下面快速查看一下存储库方法(在本例中缩短了):
public class ContactRepository : IContactRepository
{
...
public IEnumberable<Contact> LoadWithProgress(INotifyProgressChanged indicator)
{
var resultSet = new List<Contact>();
var query = ... // This is a LINQ to Entities query
// Set the maximum to the number of "pages" that will be iterated
indicator.Maximum = this.GetNumberOfPages(query.Count(), this.pageSize);
for (int i = 0; i < indicator.Maximum; i++)
{
resultSet.AddRange(query.Skip(i * this.pageSize).Take(this.pageSize));
indicator.Progress += 1; // As each "chunk" is loaded, progress is updated
}
// The complete list is returned after all "chunks" are loaded
return resultSet.AsReadOnly();
}
}
这就是存储库最终通过ViewModel向视图报告进度的方式。这是正确的方法吗?我是否正确使用了TPL,是否违反了任何主要规则等?这个解决方案是有效的,进度是按照预期报告的,我只是想确保我没有让自己失败。
这样做的"规定"方式是将TaskScheduler
实例从TaskSheduler::FromCurrentSynchronizationContext
传递到ContinueWith
,您希望确保在WPF调度程序线程上执行。
public void DoSomeLongRunningOperation()
{
// this is called from the WPF dispatcher thread
Task.Factory.StartNew(() =>
{
// this will execute on a thread pool thread
})
.ContinueWith(t =>
{
// this will execute back on the WPF dispatcher thread
},
TaskScheduler.FromCurrentSynchronizationContext());
}
我建议你避免从后台线程更新数据绑定属性。
为了解决这个问题,你可以让你的后台任务发布一个UI任务来完成它的更新,或者(甚至更好),使用IProgress<T>
/Progress<T>
系统,在基于任务的异步模式概述文档中描述。
IProgress<T>
方法很好,因为它将后台任务与ViewModel更新分开。然而,它也有一些缺点(在后台任务和更新之间共享数据;和处理异常从更新);我希望这些问题能在Async CTP正式发布之前得到解决。
我不认为你应该直接从后台线程直接更新ViewModel。我编写了很多Silverlight应用程序,我喜欢使用MVVMLight工具包来实现MVVM模式。
在MVVM中,有时你需要让ViewModel"影响"视图,这是你不能直接做的,因为ViewModel没有对视图的引用。在这些场景中,MVVMLight有一个Messenger类,允许我们在视图中"监听"消息,并从ViewModel中"通知"消息。
我认为您应该在您的场景中使用Messenger类。
下面是一个示例代码链接:http://chriskoenig.net/2010/07/05/mvvm-light-messaging/