使用 TPL 时如何在 UI 线程上调用方法?
我正在开发一个 MVVM 应用程序,它使用 TPL 在后台执行多项任务。任务需要向 UI 报告进度,以便可以更新进度对话框。由于应用程序是 MVVM,因此进度对话框绑定到名为 Progress 的视图模型属性,该属性由具有签名 UpdateProgress(intincrement)
的视图模型方法进行更新。后台任务需要调用该方法来报告进度。
我使用一种方法来更新属性,因为它允许每个任务以不同的量增加 Progress 属性。因此,如果我有两个任务,并且第一个任务花费的时间是第二个任务的四倍,则第一个任务调用 UpdateProgress(4)
,第二个任务调用 UpdateProgress(1)< /代码>。因此,当第一个任务完成时,进度为 80%,当第二个任务完成时,进度为 100%。
我的问题非常简单:如何从后台任务中调用视图模型方法?代码如下。感谢您的帮助。
这些任务使用 Parallel.ForEach()
,代码如下所示:
private void ResequenceFiles(IEnumerable<string> fileList, ProgressDialogViewModel viewModel)
{
// Wrap token source in a Parallel Options object
var loopOptions = new ParallelOptions();
loopOptions.CancellationToken = viewModel.TokenSource.Token;
// Process images in parallel
try
{
Parallel.ForEach(fileList, loopOptions, sourcePath =>
{
var fileName = Path.GetFileName(sourcePath);
if (fileName == null) throw new ArgumentException("File list contains a bad file path.");
var destPath = Path.Combine(m_ViewModel.DestFolder, fileName);
SetImageTimeAttributes(sourcePath, destPath);
// This statement isn't working
viewModel.IncrementProgressCounter(1);
});
}
catch (OperationCanceledException)
{
viewModel.ProgressMessage = "Image processing cancelled.";
}
}
语句 viewModel.IncrementProgressCounter(1)
不会引发异常,但无法通过到主线程。这些任务是从 MVVM ICommand
对象调用的,代码如下所示:
public void Execute(object parameter)
{
...
// Background Task #2: Resequence files
var secondTask = firstTask.ContinueWith(t => this.ResequenceFiles(fileList, progressDialogViewModel));
...
}
I am working on an MVVM app that performs several tasks in the background, using TPL. The tasks need to report progress to the UI so that a progress dialog can be updated. Since the app is MVVM, the progress dialog is bound to a view model property named Progress, which is updated by a view model method with the signature UpdateProgress(int increment)
. The background tasks need to call this method to report progress.
I use a method to update the property because it lets each task increment the Progress property by different amounts. So, if I have two tasks, and the first one takes four times as long as the second, the first task calls UpdateProgress(4)
, and the second task calls UpdateProgress(1)
. So, progress is at 80% when the first task completes, and at 100% when the second task completes.
My question is really pretty simple: How do I call the view model method from my background tasks? Code is below. Thanks for your help.
The tasks use Parallel.ForEach()
, in code that looks like this:
private void ResequenceFiles(IEnumerable<string> fileList, ProgressDialogViewModel viewModel)
{
// Wrap token source in a Parallel Options object
var loopOptions = new ParallelOptions();
loopOptions.CancellationToken = viewModel.TokenSource.Token;
// Process images in parallel
try
{
Parallel.ForEach(fileList, loopOptions, sourcePath =>
{
var fileName = Path.GetFileName(sourcePath);
if (fileName == null) throw new ArgumentException("File list contains a bad file path.");
var destPath = Path.Combine(m_ViewModel.DestFolder, fileName);
SetImageTimeAttributes(sourcePath, destPath);
// This statement isn't working
viewModel.IncrementProgressCounter(1);
});
}
catch (OperationCanceledException)
{
viewModel.ProgressMessage = "Image processing cancelled.";
}
}
The statement viewModel.IncrementProgressCounter(1)
isn't throwing an exception, but it's not getting through to the main thread. The tasks are called from MVVM ICommand
objects, in code that looks like this:
public void Execute(object parameter)
{
...
// Background Task #2: Resequence files
var secondTask = firstTask.ContinueWith(t => this.ResequenceFiles(fileList, progressDialogViewModel));
...
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
假设您的 ViewModel 是在 UI 线程上构建的(即:由 View 构建,或响应 View 相关事件),在我看来,情况几乎总是如此,您可以将其添加到构造函数中:
然后,当您获取事件时,您可以使用它来编组它:
编辑:
我做了一个 util 类。它是静态的,但如果您愿意,可以为其创建一个接口并使其成为非静态:
用法:
Assuming your ViewModel is constructed on the UI thread (ie: by the View, or in response to a View related event), which is the case nearly always IMO, you can add this to your constructor:
Then, when you get your event, you can use this to marshal it:
Edit:
I made an util class. It is static but if you want you can create an interface for it and make it nonstatic:
Usage:
要将方法调用编组到主 UI 线程,您可以使用 Dispatcher 的 InvokeMethod 方法。如果您使用像 Carliburn 这样的 MVVM 框架,它对 Dispatcher 具有抽象,因此您可以使用 Execute.OnUIThread(Action) 执行几乎相同的操作。
请查看这篇 Microsoft 文章,了解如何使用 Dispatcher。
To marshall method calls to the main UI thread you can use Dispatcher's InvokeMethod method. If you use MVVM Frameworks like Carliburn it has abstractions over Dispatcher so you can do almost the same thing using Execute.OnUIThread(Action).
Check this Microsoft article on how to use Dispatcher.