ParallelWork is an open source free helper class that
lets you run multiple work in parallel threads, get success,
failure and progress update on the WPF UI thread, wait for work to
complete, abort all work (in case of shutdown), queue work to run
after certain time, chain parallel work one after another.
It’s more convenient than using .NET’s
BackgroundWorker because you don’t have to declare one
component per work, nor do you need to declare event handlers to
receive notification and carry additional data through private
variables. You can safely pass objects produced from different
thread to the success callback. Moreover, you can wait for work to
complete before you do certain operation and you can abort all
parallel work while they are in-flight. If you are building highly
responsive WPF UI where you have to carry out multiple job in
parallel yet want full control over those parallel jobs completion
and cancellation, then the ParallelWork library is the right
solution for you.
I am using the ParallelWork library in my PlantUmlEditor
project, which is a free open source UML editor built on WPF. You
can see some realistic use of the ParallelWork library
there. Moreover, the test project comes with 400 lines of Behavior
Driven Development flavored tests, that confirms it really does
what it says it does.
The source code of the library is part of the
“Utilities” project in PlantUmlEditor
source code hosted at Google Code.
The library comes in two flavors, one is the ParallelWork
static class, which has a collection of static methods that you can
call. Another is the Start class, which is a fluent wrapper
over the ParallelWork class to make it more readable and
aesthetically pleasing code.
ParallelWork allows you to start work immediately on
separate thread or you can queue a work to start after some
duration. You can start an immediate work in a new thread using the
following methods:
- void StartNow(Action doWork, Action onComplete)
- void StartNow(Action doWork, Action onComplete,
Actionfailed)
For example,
ParallelWork.StartNow(() => { workStartedAt = DateTime.Now; Thread.Sleep(howLongWorkTakes); }, () => { workEndedAt = DateTime.Now;
});
Or you can use the fluent way Start.Work:
Start.Work(() => { workStartedAt = DateTime.Now; Thread.Sleep(howLongWorkTakes); }) .OnComplete(() => { workCompletedAt = DateTime.Now; }) .Run();
Besides simple execution of work on a parallel thread, you can
have the parallel thread produce some object and then pass it to
the success callback by using these overloads:
- void StartNow
(Func doWork, Action
onComplete) - void StartNow
(Func doWork, Action
onComplete, Actionfail)
For example,
ParallelWork.StartNow<Dictionary<string, string>>( () => { test = new Dictionary<string,string>(); test.Add("test", "test"); return test; }, (result) => {
Assert.True(result.ContainsKey("test"));
});
Or, the fluent way:
Start<Dictionary<string, string>>.Work(() => { test = new Dictionary<string, string>(); test.Add("test", "test"); return test; }) .OnComplete((result) => { Assert.True(result.ContainsKey("test")); }) .Run();
You can also start a work to happen after some time using these
methods:
- DispatcherTimer StartAfter(Action onComplete, TimeSpan
duration) - DispatcherTimer StartAfter(Action doWork,Action
onComplete,TimeSpan duration)
You can use this to perform some timed operation on the UI
thread, as well as perform some operation in separate thread after
some time.
ParallelWork.StartAfter( () => { workStartedAt = DateTime.Now; Thread.Sleep(howLongWorkTakes); }, () => { workCompletedAt = DateTime.Now; }, waitDuration);
Or, the fluent way:
Start.Work(() => { workStartedAt = DateTime.Now; Thread.Sleep(howLongWorkTakes); }) .OnComplete(() => { workCompletedAt = DateTime.Now; }) .RunAfter(waitDuration);
There are several overloads of these functions to have a
exception callback for handling exceptions or get progress update
from background thread while work is in progress. For example, I
use it in my PlantUmlEditor to
perform background update of the application.
// Check if there's a newer version of the app Start<bool>.Work(() => { return UpdateChecker.HasUpdate(Settings.Default.DownloadUrl); }) .OnComplete((hasUpdate) => { if (hasUpdate) { if (MessageBox.Show(Window.GetWindow(me), "There's a newer version available.
Do you want to download and install?", "New version available", MessageBoxButton.YesNo, MessageBoxImage.Information) == MessageBoxResult.Yes) { ParallelWork.StartNow(() => { var tempPath = System.IO.Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), Settings.Default.SetupExeName); UpdateChecker.DownloadLatestUpdate(Settings.Default.DownloadUrl, tempPath); }, () => { }, (x) => { MessageBox.Show(Window.GetWindow(me), "Download failed. When you run next time,
it will try downloading again.", "Download failed", MessageBoxButton.OK, MessageBoxImage.Warning); }); } } }) .OnException((x) => { MessageBox.Show(Window.GetWindow(me), x.Message, "Download failed", MessageBoxButton.OK, MessageBoxImage.Exclamation); });
The above code shows you how to get exception callbacks on the
UI thread so that you can take necessary actions on the UI.
Moreover, it shows how you can chain two parallel works to happen
one after another.
Sometimes you want to do some parallel work when user does some
activity on the UI. For example, you might want to save file in an
editor while user is typing every 10 second. In such case, you need
to make sure you don’t start another parallel work every 10
seconds while a work is already queued. You need to make sure you
start a new work only when there’s no other background work
going on. Here’s how you can do it:
private void ContentEditor_TextChanged(object sender, EventArgs e) { if (!ParallelWork.IsAnyWorkRunning()) { ParallelWork.StartAfter(SaveAndRefreshDiagram, TimeSpan.FromSeconds(10)); } }
If you want to shutdown your application and want to make sure
no parallel work is going on, then you can call the
StopAll() method.
ParallelWork.StopAll();
If you want to
wait for parallel works to complete without a timeout, then you can
call the WaitForAllWork(TimeSpan timeout). It will block the
current thread until the all parallel work completes or the timeout
period elapses.
result = ParallelWork.WaitForAllWork(TimeSpan.FromSeconds(1));
The result is
true, if all parallel work completed. If it’s false, then the
timeout period elapsed and all parallel work did not complete.
For details how this library is built and how it works, please
read the following codeproject article:
ParallelWork: Feature rich multithreaded fluent task
execution library for WPF
http://www.codeproject.com/KB/WPF/parallelwork.aspx
If you like the article, please vote for me.