Continuing with the theme of long running operations, in this post I'll take a look at how to use the new Task API in .NET 4.0 to easily perform a long running operation in the background and return the results back on the thread that created the Task
. Since this wasn't immediately obvious to me when reading the documentation, I figured it might be of some help to others.
When we create a new Task
, we are telling the framework that we have a piece of work that needs to be completed. We aren't specifying how that work should be completed, as that is the job of the TaskScheduler
. If we want to be notified when a Task
completes, we can pass a delegate to the Task
via the ContinueWith
method like so:
Task t = Task.Factory.StartNew(LongRunningOperation);
t.ContinueWith(OperationCompletedCallback);
t.Start();
Since the ContinueWith
method is actually constructing a new Task
, it too will use a TaskScheduler
to determine how it is run. The default TaskScheduler
uses the ThreadPool
to parallelize the Tasks
, so both our original task and our callback function will be run in ThreadPool
worker threads.
In order to get the completion Task
to run on the main thread, the trick is to pass it a different TaskScheduler
. This is accomplished through the TaskScheduler.FromCurrentSynchronizationContext
static method. We have to use an overload of the ContinueWith
method to pass in a scheduler:
Task t = Task.Factory.StartNew(LongRunningOperation);
t.ContinueWith(OperationCompletedCallback,
TaskScheduler.FromCurrentSynchronizationContext());
t.Start();
If you are calling this Task
from a thread where System.Threading.SynchronizationContext.Current
returns a valid context, the ContinueWith
method will be called on the thread that started the Task
. If you are running within the context of WPF or WinForms, you shouldn't have to worry about this not being valid.
I did run into a problem when using MAF (the whole System.AddIn stuff) where the SynchronizationContext
was null for my addins. It was easy enough to create one:
DispatcherSynchronizationContext context =
new DispatcherSynchronizationContext(Dispatcher.CurrentDispatcher);
-AH