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));
...
}
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.