Parallelism in .NET – Part 16, Creating Tasks via a TaskFactory
- by Reed
The Task class in the Task Parallel Library supplies a large set of features. However, when creating the task, and assigning it to a TaskScheduler, and starting the Task, there are quite a few steps involved. This gets even more cumbersome when multiple tasks are involved. Each task must be constructed, duplicating any options required, then started individually, potentially on a specific scheduler. At first glance, this makes the new Task class seem like more work than ThreadPool.QueueUserWorkItem in .NET 3.5.
In order to simplify this process, and make Tasks simple to use in simple cases, without sacrificing their power and flexibility, the Task Parallel Library added a new class: TaskFactory.
The TaskFactory class is intended to “Provide support for creating and scheduling Task objects.” Its entire purpose is to simplify development when working with Task instances. The Task class provides access to the default TaskFactory via the Task.Factory static property. By default, TaskFactory uses the default TaskScheduler to schedule tasks on a ThreadPool thread. By using Task.Factory, we can automatically create and start a task in a single “fire and forget” manner, similar to how we did with ThreadPool.QueueUserWorkItem:
Task.Factory.StartNew(() => this.ExecuteBackgroundWork(myData) );
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
This provides us with the same level of simplicity we had with ThreadPool.QueueUserWorkItem, but even more power. For example, we can now easily wait on the task:
// Start our task on a background thread
var task = Task.Factory.StartNew(() => this.ExecuteBackgroundWork(myData) );
// Do other work on the main thread,
// while the task above executes in the background
this.ExecuteWorkSynchronously();
// Wait for the background task to finish
task.Wait();
TaskFactory simplifies creation and startup of simple background tasks dramatically.
In addition to using the default TaskFactory, it’s often useful to construct a custom TaskFactory. The TaskFactory class includes an entire set of constructors which allow you to specify the default configuration for every Task instance created by that factory.
This is particularly useful when using a custom TaskScheduler. For example, look at the sample code for starting a task on the UI thread in Part 15:
// Given the following, constructed on the UI thread
// TaskScheduler uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
// When inside a background task, we can do
string status = GetUpdatedStatus();
(new Task(() =>
{
statusLabel.Text = status;
}))
.Start(uiScheduler);
This is actually quite a bit more complicated than necessary. When we create the uiScheduler instance, we can use that to construct a TaskFactory that will automatically schedule tasks on the UI thread. To do that, we’d create the following on our main thread, prior to constructing our background tasks:
// Construct a task scheduler from the current SynchronizationContext (UI thread)
var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
// Construct a new TaskFactory using our UI scheduler
var uiTaskFactory = new TaskFactory(uiScheduler);
If we do this, when we’re on a background thread, we can use this new TaskFactory to marshal a Task back onto the UI thread. Our previous code simplifies to:
// When inside a background task, we can do
string status = GetUpdatedStatus();
// Update our UI
uiTaskFactory.StartNew( () => statusLabel.Text = status);
Notice how much simpler this becomes! By taking advantage of the convenience provided by a custom TaskFactory, we can now marshal to set data on the UI thread in a single, clear line of code!