TPL: Background thread completion notification?
I have an MVVM application that processes a large number of images in the background, using the .NET 4.0 Task Parrallel Library. The processing is done on a background thread, which posts its progress to a view model property that is bound to a progress dialog. That part is working okay. But the background thread also needs to notify the main thread when all processing is done, so that the progress dialog can be closed.
Here's my dilemma: With my current code, the 'processing ending' statement gets hit as so开发者_StackOverflow中文版on as the background task is set up. But if I insert a task.Wait()
statement between the two, it appears to block the UI thread, preventing progress updates. So, how does the background thread signal completion to the main thread? Thanks for your help.
Here is the code on the UI thread that creates the background task:
/* The view should subscribe to the two events referenced below, to
* show and close a progress indicator, such as a Progress dialog. */
// Announce that image processing is starting
m_ViewModel.RaiseImageProcessingStartingEvent();
// Process files as a background task
var task = Task.Factory.StartNew(() => DoWork(fileList, progressDialogViewModel));
// Announce that image processing is finished
m_ViewModel.RaiseImageProcessingEndingEvent();
And here is the DoWork()
method on the background thread. It processes the image files, using a Parallel.ForEach()
statement:
private void DoWork(IEnumerable<string> fileList, ProgressDialogViewModel viewModel)
{
// Declare local counter
var completedCount = 0;
// Process images in parallel
Parallel.ForEach(fileList, imagePath =>
{
ProcessImage(imagePath);
Interlocked.Increment(ref completedCount);
viewModel.Progress = completedCount;
});
}
You need to add a continuation to the background task which will fire on the current Dispatcher
(SynchronizationContext
) thread. That would look like this:
task.ContinueWith(t =>
{
// This will fire on the Dispatcher thread
m_ViewModel.RaiseImageProcessingEndingEvent();
},
TaskScheduler.FromCurrentSynchronizationContext());
Using the overload of ContinueWith
that specifies the TaskScheduler
and, more specifically, using FromCurrentSynchronizationContext
ensures that your continuation logic will fire on the WPF Dispatcher thread where it is safe to access UI elements.
That said, I should also point out that you probably don't want to be modifying viewModel.Progress property because, if UI elements are bound to it, they will be updated from a background thread which is no goo. Instead you should spin off a task inside there as well to ensure the notification is propagated on the correct thread. The trick to that though is that your DoWork doesn't know the current synchronization context at the point where it's launched, so you would need to pass it in when you kick off the original task.
UPDATE: see my follow up comment for why I struck my last paragraph on updating the progress property.
精彩评论