This is a little library that I put together to provide a fluent API over the .NET BackgroundWorker class.
FluentWorker sample usage
- FluentWorker
- .Do(SomeVoidMethod)
- .WhenDone(() => Console.WriteLine("Done!"))
- .Start();
FluentWorker addresses what I see as the burdensome repetition of employing BackgroundWorker, which typically involves:
- instantiation
- several lines of settings
- multiple event subscriptions
- creating handlers for all of the events
- starting the
BackgroundWorker
- disposing of the
BackgroundWorker
As point of reference, the following is roughly the equivalent of the 4 lines shown above:
Straight-up BackgroundWorker
- // …
- var worker = new BackgroundWorker();
- worker.DoWork += worker_DoWork;
- worker.RunWorkerCompleted
- += worker_RunWorkerCompleted;
- worker.RunWorkerAsync();
- }
-
- static void worker_DoWork
- (object sender, DoWorkEventArgs e)
- {
- SomeVoidMethod();
- ((BackgroundWorker) sender).DoWork
- -= worker_DoWork;
- }
-
- static void worker_RunWorkerCompleted(object sender,
- RunWorkerCompletedEventArgs e)
- {
- Console.WriteLine("Done!");
- var worker = (BackgroundWorker) sender;
- worker.RunWorkerCompleted
- -= worker_RunWorkerCompleted;
- worker.Dispose();
- }
…and this is (1) for the simplest case, and (2) required for every usage of
BackgroundWorker. Adding progress notifications and cancellation support require more lines of configuration and yet another event handler delegate.
FluentWorker can reduce virtually all of this to a single expression in the consumer. While you may still use multiple lines, the code is terse and declarative.
FluentWorker handles event subscription and unsubscription internally. Rather than creating handler methods in your classes, you supply simple
void delegates, which can be in-line/anonymous
Actions or member methods.
An arbitrary number of actions can be added to the worker's task list by calling
Do() repeatedly, and an arbitrary number of actions can executed upon completion of those tasks by calling
WhenDone() repeatedly. Tasks will be executed synchronously on the background thread. This can be used to partition and "queue" tasks, and to keep progress reporting logic out of the processing logic.
Adding multiple Do() and WhenDone() actions
- FluentWorker
- .Do(FirstTaskMethod)
- .Do(t => t.ReportProgress(25))
- .Do(SecondTaskMethod)
- .Do(t => t.ReportProgress(75))
- .Do(() => Console.WriteLine("ThirdMethodInline"))
- .Do(t => t.ReportProgress(100))
- .WhenDone(DataLoaded)
- .WhenDone(() => Console.WriteLine("Done!"))…
Subsequent calls to
Start() will repeat the worker's set of actions. If a worker is running when
Start() is called, another run will be queued for that worker and the
WhenDone() actions will execute only once. If a worker is not running, it will spin up a new
BackgroundWorker internally and
WhenDone() actions will be executed when it completes its work.
FluentWorker can be configured to handle exceptions with a custom
Action<Exception> and
Continue(),
Cancel() or
Throw() the
Exception.
Exception action and continuation strategy
- FluentWorker
- .Do(SomeVoidMethod)
- .OnException(x => x.Do(
- ex => Log.Error(ex, "Error in bg process")).Continue())…
A thread-safe progress reporting strategy can be defined by supplying an
Action<int>.
Setting the progress-reporting strategy
- FluentWorker
- .Do(SomeVoidMethod)
- .ReportsProgressBy(i => progressBar.Value = i)…
Progress reporting and cancellation-checking is simplified in
Do() task delegates by using the
Do(ITaskHelper) overload.
Using ITaskHelper to check for cancellation
and invoke the progress-reporting strategy
- FluentWorker.Do(t =>
- {
- t.ReportProgress(0);
- // do something
- t.ReportProgress(50);
- if(t.Cancelled) return;
- // do something else
- if(t.Cancelled) return;
- t.ReportProgress(100);
- })
Calling
ReportProgress(i) will execute your configured
ReportsProgressBy() Action<int>.
Calling
Restart() on a
FluentWorker instance is like calling
Cancel() and
Start() in succession, except that
Restart() will always kick off a new
BackgroundWorker immediately, even if active
BackgroundWorkers are still winding down. A
BackgroundWorker that is canceled as a result of a call to
Restart() will not execute its
WhenDone() actions; they will be executed when the new worker completes processing.
FluentWorker disposes of its
BackgroundWorker automatically when processing is complete. As mentioned previously, a subsequent call to
Start() will spin up a new
BackgroundWorker() if none is active. Lifecycle management of the component is trasparent to the consumer.
Disclaimer
There is a lot of room here for expansion and--no doubt--improvement. I'm no expert in multithreading, and haven't put this code through any serious paces, so of course the supplied code comes as-is, without warranty, express or implied.
Summary
There are myriad ways to stow
BackgroundWorker's heavy baggage safely and securely. FluentWorker is my first pass at encapsulating the complexity and re-exposing
BackgroundWorker's powerful functionality in a simple, reusable component with a clean, fluent interface.
Downloads
I gave a brief presentation on this at tonight's
ONETUG meeting. The slides are from that presentation.
FluentWorker_v20110421.zip |
FluentWorkerSlides.zip