Contents
This article explores the new VS2010 System.Threading.Tasks
namespace that has come about due to the ongoing work that is being done with the Microsoft Parallel Extensions to .NET Framework 3.5, June 2008 Community Technology Preview.
As this article is using this namespace, it obviously requires VS2010, so if you haven't got that, sorry but the reading is over I guess. However, if you are still curious, carry on, and let's have a look at what this new namespace can do for us.
The current support we have for running small work items of code on a background thread, is really to use the System.Threading.ThreadPool
, which I have to say I am a real fan of. I actually discuss ThreadPool
in detail in one of my old threading articles, should you be interested:
The reason I like the ThreadPool
is that the ThreadPool
does all the nasty thread management stuff for me, and schedules my work item for me when it has time to run it. You would currently use the ThreadPool
something like this:
using System;
using System.Threading;
namespace QueueUserWorkItemWithState
{
public class ReportData
{
public int reportID;
public ReportData(int reportID)
{
this.reportID = reportID;
}
}
class Program
{
static void Main(string[] args)
{
ReportData rd = new ReportData(15);
ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadProc), rd);
Console.WriteLine("Main thread does some work, then sleeps.");
Thread.Sleep(1000);
Console.WriteLine("Main thread exits.");
Console.ReadLine();
}
static void ThreadProc(Object stateInfo)
{
ReportData rd = (ReportData)stateInfo;
Console.WriteLine(string.Format("report {0} is running", rd.reportID));
}
}
}
Which is, of course, a much better solution than managing our own threads. However, there are several problems with the current ThreadPool
object, such as:
- Work items can't return a value (however
BeginInvoke
/EndInvoke
delegates do return values) - Can not cancel tasks
- No waiting on work items, unless you use external synchronization methods such as
WaitHandle
s etc. - Composing a group of items in a structured way
- Handling exceptions thrown concurrently or any other richer construct built on top of it
In fact, the way the current ThreadPool
works is, you just chuck some work at it and forget about it. Hmmm, surely there is a better way.
In the past, I would have remedied this by using the absolutely rock solid and brilliant SmartThreadPool by Ami Bar, which fixes these and many other issues, but today, I had a bit of time on my hands, so I thought I would look into the new VS2010 System.Threading.Tasks
namespace and see what it had to offer.
And I have to say, from what I have seen, it looks like it will offer a lot; let's carry on and have a look, shall we?
Before we start, let me just mention the demo app. The demo app is a Visual Studio 2010 WPF project, so you will need VS2010 to run it.
When run, it looks like this, and the basic idea is dead simple: run some background Task
that fills the ListBox
with data items. That is it.
Here is a screenshot of the demo app in all its glory; pretty, ain't she?
As can be seen from the demo app screenshot above, there are seven buttons, which seem to do different things. So what do these seven buttons actually do? Well, the buttons do the following:
- Run a
Task
with no state passed to the Task
- Run a
Task
with state passed to the Task
- Create and run a
Task
using the TaskFactory
which returns a result - Create and run a
Task
, which uses a Continuation - Start a canceallable
Task
- Cancel a canceallable
Task
- A weird one, is it broken, or not?
So I think the best way to carry on is to look at each of these seven areas one by one. So let's proceed to do that.
Run a Task With No State Passed to the Task
This is by far the easiest setup to use, as all we really need to do is create a Task
and Start it. We do this quite easily as the Task
s constructor takes a Action
(delegate) which is a pointer to the Task
's payload that will be run when the Task
is started. The example below shows this, and it should be noted that the UI thread will simply continue and will not block waiting for the Task
, so this is how you might setup a background activity. It should also be noted that by using the Task
class, it is not possible to obtain a return value. It would, however, be possible if we use the Task<T>
, which we shall cover in a while.
private void TaskNoState_Click(object sender, RoutedEventArgs e)
{
Task task = new Task((Action)DoSomething);
items.Clear();
task.Start();
lstItems.ItemsSource = items;
MessageBox.Show("TaskNoState DONE");
}
private void DoSomething()
{
StringBuilder build = new StringBuilder();
build.AppendLine("This simple method demostrates how to use a Task with no state");
build.AppendLine("That means you can only really do something that doesn't");
build.AppendLine("need an input value, and doesn't return anything");
MessageBox.Show(build.ToString());
}
Run a Task With State Passed to the Task
This time, all that we are doing is passing a single Object
which represents the Task
state data to the actual Task
. Otherwise, it is the same arrangement as the example shown above; we are still just using an Action
delegate. The difference this time is that the Action
delegate is of type Action<T>
, so it can accept a parameter. Here is an example of setting up a Task
to accept some Object
state. As before, the example below allows the UI thread to simply continue, and will not block waiting for the Task
, so this is how you might setup a background activity.
private void TaskState_Click(object sender, RoutedEventArgs e)
{
Task task = new Task((Action<Object>)CreateStuffForTaskState,
new ObjectState<Int32>
{
CurrentTask = "TaskState",
Value = 999999
});
items.Clear();
task.Start();
items = new ObservableCollection<string>(taskItems);
lstItems.ItemsSource = items;
MessageBox.Show("TaskState DONE");
}
private void CreateStuffForTaskState(Object o)
{
var state = o as ObjectState<Int32>;
taskItems.Clear();
for (int i = 0; i < state.Value; i++)
{
taskItems.Add(String.Format("{0} Item {1}", state.CurrentTask, i));
}
}
In the example above, the state data is represented by this simple class, which is used anywhere where a Task State is used in the demo app.
internal class ObjectState<T>
{
public String CurrentTask { get; set; }
public T Value { get; set; }
}
Create and Run a Task Using the TaskFactory Which Returns a Result
So far, we have seen how to queue up delegates within Task
s that get scheduled to run, which is all cool, but sometimes, we need a return result from our background activity, say if the background activity was to fetch the next 10,000 records from the database. For this operation, we may actually need a return value. So, can the System.Threading.Tasks
namespace deal with return values? The answer is yes, it sure can. All we need to do with the Task
s is set them up using the generic Task<T>
type and use some Func<T>
delegates as the payload to run for the Task<T>
.
Here is an example; also note that in this example, I am using the TaskFactory
for creating and starting a new Task
; I think this is the preferred method.
private void TaskFactWithReturnValue_Click(object sender, RoutedEventArgs e)
{
Func<ObservableCollection<String>> obtainTaskResults = TaskWithResults;
Task<ObservableCollection<String>> task =
Task.Factory.StartNew<ObservableCollection<String>>(obtainTaskResults,
TaskCreationOptions.DetachedFromParent);
items.Clear();
task.Wait();
items = task.Result;
lstItems.ItemsSource = items;
MessageBox.Show("TaskFactWithReturnValue DONE");
}
private ObservableCollection<String> TaskWithResults()
{
ObservableCollection<String> results = new ObservableCollection<string>();
for (int i = 0; i < 10; i++)
{
results.Add(String.Format("TaskFactWithReturnValue Item {0}", i));
}
return results;
}
You can see in this example above, as we actually want a return value, I am waiting for the Task
to complete using the Task.Wait()
method which blocks the UI thread, and then I am using Task.Result
to update the UI. I do not like the fact that the UI thread is blocked, but I can not see what you can do apart from wait if you are needing the results. TaskWait()
does, of course, have an overload that can accept a TimeSpan
timeout, so we could alleviate this waiting by doing something like task.Wait(2500)
, which waits for 2.5 seconds only.
Create and Run a Task, Which Uses a Continuation
So we have just seen that we can create a Task
that returns a value, which is really nice and not something that the current ThreadPool
does. What else can these Task
s do? Well, they have another trick or two up their sleeves; one such trick is something called a Continue, which is essentially another Task
to run after the current Task
completes. By using Task
s and Continuations, you can sequence Task
s in a certain order without the use of external WaitHandle
s such as ManualResetEvent
/AutoResetEvent
.
This is easily achieved using the Task.ContinueWith<T>()
method, as shown below.
Here is an example that creates two Task
s that go into forming one final set of result values that are then shown on the UI:
private void TaskContinue_Click(object sender, RoutedEventArgs e)
{
#region SYNTAX EXAMPLE 1 (Comment to try SYNTAX EXAMPLE 2)
Func<Object, ObservableCollection<String>> obtainTaskResultsFunc =
TaskWithResultsWithState;
Task<ObservableCollection<String>> task =
Task.Factory.StartNew(obtainTaskResultsFunc, new ObjectState<Int32>
{
CurrentTask = "TaskState",
Value = 2
});
Func<Task, ObservableCollection<String>> contineResultsFunc =
ContinueResults;
Task<ObservableCollection<String>> continuationTask =
task.ContinueWith<ObservableCollection<String>>(contineResultsFunc,
TaskContinuationOptions.OnlyOnRanToCompletion);
continuationTask.Wait();
items.Clear();
items = continuationTask.Result;
#endregion
#region SYNTAX EXAMPLE 2 (UnComment to try And Comment SYNTAX EXAMPLE 1)
#endregion
lstItems.ItemsSource = items;
MessageBox.Show("TaskContinue DONE");
}
This would produce something like this in the UI when run:
It can be seen that the first two results came from the original Task
, and the rest came from the Continuation Task
that was only started when the first Task
completed.
Create a Task and Cancel It
Another advertised nice feature of Task
s is that they can be cancelled. A Task
can be requested to be cancelled, but as a Task
is actually just a delegate that is running, there needs to be some co-operation in the Task
's payload delegate, which inspects certain aspects about the running Task
and takes the correct route based on the Task
's current settings.
Here is an example where the Task
is extremely low running, and the UI doesn't block and the Task
is running in the background, but the user may cancel the Task
. Obviously, the work that is done in the Task
's payload delegate must make sure that it knows what to do with a cancellation request, and act accordingly.
private void TaskCancelStart_Click(object sender, RoutedEventArgs e)
{
if (cancelableTask != null &&
cancelableTask.Status == TaskStatus.Running)
return;
try
{
cancelableTask = new Task((Action)DoSomethingWithCancel);
items.Clear();
cancelableTask.Start();
items = new ObservableCollection<string>(taskItems);
lstItems.ItemsSource = items;
}
catch (Exception ex)
{
MessageBox.Show(String.Format("TaskCancel Exception {0}",
ex.InnerException != null ? " : " +
ex.InnerException.Message : String.Empty));
}
}
private void TaskCancelStop_Click(object sender, RoutedEventArgs e)
{
cancelableTask.Cancel();
}
private void DoSomethingWithCancel()
{
taskItems.Clear();
for (int i = 0; i < Int32.MaxValue; i++)
{
if (!cancelableTask.IsCancellationRequested)
taskItems.Add(String.Format("TaskCancel Item {0}", i));
else
{
Task.Current.AcknowledgeCancellation();
break;
}
}
}
It can be seen that there is a Task.Cancel()
method which can be used to request a Task
cancellation, and there is also a IsCancellationRequested
property which can be inspected to see if a cancellation has been requested. From there, the Task
's payload delegate must play fair and acknowledge the cancellation by using the Task.AcknowledgeCancellation()
method.
A Weird One, Not All Plain Sailing
While I am quite impressed by the new Task
functionality, I did do some experimenting, such as running this bit of code using a Task<T>
which, as we now know, should return a result.
private void IsThisBroken_Click(object sender, RoutedEventArgs e)
{
Func<ObservableCollection<String>> obtainTaskResults =
TaskWithResultsWhyIsItBlockingUI;
Task<ObservableCollection<String>> task =
Task.Factory.StartNew<ObservableCollection<String>>(obtainTaskResults,
TaskCreationOptions.DetachedFromParent);
items.Clear();
items = task.Result;
lstItems.ItemsSource = items;
MessageBox.Show("IsThisBroken DONE");
}
private ObservableCollection<String> TaskWithResultsWhyIsItBlockingUI()
{
ObservableCollection<String> results = new ObservableCollection<string>();
for (int i = 0; i < Int32.MaxValue; i++)
{
results.Add(String.Format("TaskFactWithReturnValue Item {0}", i));
}
return results;
}
Now when I run this bit of code, exactly as it is shown, the UI becomes unresponsive, which puzzled me slightly as we are not using Task.Wait()
(it is commented out above) which would block the calling Thread
until the Task
is completed, but the UI is clearly blocking or is not responsive. Try the demo for yourself. Using the demo code, keep the Task<T>
using the Int32.MaxValue
and put a breakpoint on the line in the IsThisBroken_Click()
method of the demo app.
items = task.Result;
So I started looking into this a bit more, and I thought I wonder if a Task
that returns something using the Task<T>.Result
property blocks the caller until the Task<T>
is completed and has a return value.
So what I did was:
- Comment out the two lines above dealing with the items, so these lines now became:
MessageBox.Show("IsThisBroken DONE");
And guess what, the UI was responsive straight away, though I have now lost the ability to use the Task<T>
return value obviously.
- So I then put the original code back in, and just decreased the payload of the
Task<T>
so it used a shorter loop value; so instead of Int32.MaxValue
, I used 50,000, and then too, I saw that the code jumped straight back to a breakpoint that I set on the line:
items = task.Result;
This kind of proved to me that there appears to be a blocking action that the Task<T>.Result
property enforces. This actually makes sense when you think about it, but it is something to be aware of. I think a good way to keep track of long running Task
s would be to periodically check the Task
status using the TaskStatus
enum, where you could use something like TaskStatus.RanToCompletion
. All this said, Task
s do provide quite a nice API to use, and I think they are a bit better than the ThreadPool
API we currently have.
Other useful links about Tasks:
Task related links at the Task Parallel Library site:
That is all I think I wanted to say about Tasks, but I am sure you will agree, these look pretty good, and are a welcome addition to the current System.Threading
namespace.
Thanks
As always, votes / comments are welcome.