Task Parallel Library: 6 of n
Demo code source : Tasks6.zip
Introduction
This is the 6th and final part of my proposed series of articles on TPL. Last time I introduced Pipelines, and covered this ground:
- BlockingCollection
- BlockingCollection Basics
- Simple Pipeline
- More Complex Pipeline
This time we are going to be looking at some of the more advances TPL things you can do right now, which will conclude what we can do with TPL using the current .NET 4.0 classes available to us.
We will then proceed to look at what we will be able to do with C# 5 by using the new Async CTP
Article Series Roadmap
This is article 5 of a possible 6, which I hope people will like. Shown below is the rough outline of what I would like to cover.
- Starting Tasks / Trigger Operations / ExceptionHandling / Cancelling / UI Synchronization
- Continuations / Cancelling Chained Tasks
- Parallel For / Custom Partioner / Aggregate Operations
- Parallel LINQ
- Pipelines
- Advanced Scenarios / v.Next For Tasks (this article)
Table Of Contents
Pre-Requisites
As this article uses some new Community Technology Preview (CTP) bits that are not yet part of the .NET Framework you will need to download the Async CTP Refresh SP1 (which is what this article is based on), this can be downloaded right here:
http://www.microsoft.com/downloads/en/details.aspx?FamilyID=4738205d-5682-47bf-b62e-641f6441735b
Finishing Up TPL
This small section will hopefully finish up the few last remaining bits and peices that I still wanted to go through with TPL as it stands right now, you know with the classes you have available to you in .NET 4.0
AsyncFrom
Demo project name : AsyncFromBeginEnd/WCFService1
One of the neat things you can do with TPL is to use TPL with the older
Asynchronous Programming Model (APM) by the use of TPLs inbuilt FromAsync
which expects to create a Task based on the old familiar Begin/End
methods that worked with a IAsyncResult
interface. To demonstrate
this I have constructed a rather simple WCF service (WCFService1 in the attached
demo code), which I have then added a referennce to which also supports being
called Asynchnously, as such there is the typical APM Begin/End
IAsyncResult
based methods that one would expect when working with APM.
Here is what the WCF service contract looks like.
[ServiceContract]
public interface IService1
{
[OperationContract]
List<String> GetData(int numberOf);
}
Which when added as a Service Reference with the async method support has proxy methods available as follows:
namespace AsyncFromBeginEnd.ServiceReference1 {
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")]
[System.ServiceModel.ServiceContractAttribute(ConfigurationName="ServiceReference1.IService1")]
public interface IService1 {
[System.ServiceModel.OperationContractAttribute(
Action="http://tempuri.org/IService1/GetData",
ReplyAction="http://tempuri.org/IService1/GetDataResponse")]
System.Collections.Generic.List<string> GetData(int numberOf);
[System.ServiceModel.OperationContractAttribute(AsyncPattern=true,
Action="http://tempuri.org/IService1/GetData",
ReplyAction="http://tempuri.org/IService1/GetDataResponse")]
System.IAsyncResult BeginGetData(int numberOf, System.AsyncCallback callback, object asyncState);
System.Collections.Generic.List<string> EndGetData(System.IAsyncResult result);
}
....
....
}
So how can we get a TPL Task
from that older APM style code. Well it is actually very straight forward we simple
do the following:
class Program
{
static void Main(string[] args)
{
ServiceReference1.Service1Client client =
new ServiceReference1.Service1Client();
Task<List<String>> task =
Task<List<String>>.Factory.FromAsync(
client.BeginGetData(10, null, null),
ar => client.EndGetData(ar));
List<String> result = task.Result;
Console.WriteLine("Successfully read all bytes using a Task");
foreach (string s in result)
{
Console.WriteLine(s);
}
Console.ReadLine();
}
}
And just to prove it all works here is the output
TaskCompletionSource
Demo project name : TaskCompletionSource1/TaskCompletionSource2
TaskCompletionSource<T>
is a weird beast that could be used when you may need to
produce a Task. In MSDNs own words
Represents the producer side of a System.Threading.Tasks.Task{TResult} unbound to a delegate, providing access to the consumer side through the System.Threading.Tasks.TaskCompletionSource<TResult>.Task property.
You can do all the normal things that you would expect a Task
to do, such as
have a result, Exception, Cancelled property, that you can set which would
simulate what a Task
would have done. I think the best way to see what this is
all about is to see a couple of examples. So the 1st example is heavily
borrowed directly from MSDN and looks like this:
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace TaskCompletionSource1
{
/// <summary>
/// This example is adapted from Microsoft MSDN code freely available at
/// http://msdn.microsoft.com/en-us/library/dd449174.aspx
/// </summary>
class Program
{
static void Main(string[] args)
{
TaskCompletionSource<String> tcs1 =
new TaskCompletionSource<String>();
Task<String> task1 = tcs1.Task;
// Complete tcs1 in background Task
Task.Factory.StartNew(() =>
{
Thread.Sleep(1000);
tcs1.SetResult("Task1 Completed");
});
// Waits 1 second for the result of the task
Stopwatch sw = Stopwatch.StartNew();
String result = task1.Result;
sw.Stop();
Console.WriteLine("(ElapsedTime={0}): t1.Result={1} (expected \"Task1 Completed\") ",
sw.ElapsedMilliseconds, result);
TaskCompletionSource<String> tcs2 =
new TaskCompletionSource<String>();
Task<String> task2 = tcs2.Task;
// Raise Exception tcs2 in background Task
Task.Factory.StartNew(() =>
{
Thread.Sleep(1000);
tcs2.SetException(new InvalidProgramException("Oh no...Something is wrong"));
});
sw = Stopwatch.StartNew();
try
{
result = task2.Result;
Console.WriteLine("t2.Result succeeded. THIS WAS NOT EXPECTED.");
}
catch (AggregateException e)
{
Console.Write("(ElapsedTime={0}): ", sw.ElapsedMilliseconds);
Console.WriteLine(
"The following exceptions have been thrown by t2.Result: (THIS WAS EXPECTED)");
for (int j = 0; j < e.InnerExceptions.Count; j++)
{
Console.WriteLine("\n-------------------------------------------------\n{0}",
e.InnerExceptions[j].ToString());
}
}
Console.ReadLine();
}
}
}
And has this output when run:
The 2nd example illustates a rather novel use of a TaskCompletionSource<T>
where
we use it to return a Task
which has been delayed.
class Program
{
static void Main(string[] args)
{
Stopwatch watch = new Stopwatch();
watch.Start();
Task<DateTimeOffset> delayTask = Delay(5000);
Console.WriteLine(String.Format("Ellapsed Time 1 : {0}", watch.Elapsed));
delayTask.ContinueWith((x) =>
{
Console.WriteLine(String.Format("Ellapsed Time 2 : {0}", watch.Elapsed));
});
Console.ReadLine();
}
public static Task<DateTimeOffset> Delay(int millisecondsTimeout)
{
var tcs = new TaskCompletionSource<DateTimeOffset>();
new Timer(self =>
{
((IDisposable)self).Dispose();
tcs.TrySetResult(DateTime.Now);
}).Change(millisecondsTimeout, - 1);
return tcs.Task;
}
}
And has this output when run:
That concludes what I wanted to say about TPL and what we have now in .NET 4 land, hope you have enjoyed the ride, next up we will spend some time looking into what things might be like for us in .NET 5 land.
Async CTP
Recently Microsoft released the Async CTP which is now in its 2nd CTP release, and a few things have changed (which I will not be mentioning in this article), this article will instead concentrate on what is allowable right now using the latest Async CTP (which was Async CTP Refresh SP1 at the time of writing this article).
One thing of particular note is that some of the code demonstrated in this
section will use the CTP TaskEx
classes which will eventually
become part of the regular Task API.
What Is The Async CTP All About
So what is so special about the Async CTP, we were quite happy with using
Task
(s) and the current TPL goodness no? Well to be honest yes, but
all is not lost, far from it. The Async CTP simply builds apon Task
s/TPL
in a very elegant manner. It does this by introducing 2 new key words and by
supplying a whole bunch of new extension methods which transform the existing
.NET classes into Awaitables (which is something we will cover very shortly).
The new key words are
Async : Which is something that must be used on methods that
will have some asynchronous code running in them, that you wish to Await
on.
Await : Simply allows us to wait for a Task
(or
custom Awaiter
based object) result to be obtained.
This may not sound much, but what it does do for you is really cleans up your
async code base to the point where it looks the same as a synchronous version
would look. There is no changes to the program flow, no nasty call this callback
for error, call this callback for success, not a IAsyncResult
in
sight. And to be honest to convert a synchronous method into a asynchronous
method would be very very easily to do using the Async CTP.
We will see more of these key words as we progress, so lets carry on shall we.
Our 1st Simple Async Example
Demo project name : SimpleAsync
Lets start with an insanely trivial example shall we, here is the code, not
the highlighted Async/Await
key words there, and also note that
GetString() method actually returns a Task<String> even though the method body
simple returns a String object. Neat huh.
Which outputs this when run:
So what exactly going on there, we have a Start()
method that is
marked up with the new Async
key word, which tells the compiler
that this method will be run asynchronously, and then we have a String
which is returns via the GetString()
method, that really
returns a Task<String>
that we are awaiting on using the new
Await
key word, when we are really returning a String
in the
GetString()
method body. Huh?
Well to break it down a bit, the compiler does some work for you (which you
will see in a minute) that know what to emit when it sees an Async
key word being used. The next peice to the puzzle is that Task
has
been vamped up to become a Awaitable object (more on this later), and thanks
also to the CTP is that we can return a Task<String>
by simply
returning a String
in the GetString()
method body.
What the compiler does is treat the code after the Await
as a
continuation that is run after the work the Await
keyword has run
to completion.
If we look at what we get in Reflector for this trivial example, we can see that it is converted into some sort of state machine type code.
internal class Program
{
// Methods
private Task<string> GetString()
{
<GetString>d__5 d__ = new <GetString>d__5(0);
d__.<>4__this = this;
d__.<>t__MoveNextDelegate = new Action(d__, (IntPtr) this.MoveNext);
d__.$builder = AsyncTaskMethodBuilder<string>.Create();
d__.MoveNext();
return d__.$builder.Task;
}
private static void Main(string[] args)
{
new Program().Start();
}
private void Start()
{
<Start>d__0 d__ = new <Start>d__0(0);
d__.<>4__this = this;
d__.<>t__MoveNextDelegate = new Action(d__, (IntPtr) this.MoveNext);
d__.$builder = AsyncVoidMethodBuilder.Create();
d__.MoveNext();
}
// Nested Types
[CompilerGenerated]
private sealed class <GetString>d__5
{
// Fields
private bool $__disposing;
public AsyncTaskMethodBuilder<string> $builder;
private int <>1__state;
public Program <>4__this;
public Action <>t__MoveNextDelegate;
public StringBuilder <sb>5__6;
// Methods
[DebuggerHidden]
public <GetString>d__5(int <>1__state)
{
this.<>1__state = <>1__state;
}
[DebuggerHidden]
public void Dispose()
{
this.$__disposing = true;
this.MoveNext();
this.<>1__state = -1;
}
public void MoveNext()
{
string <>t__result;
try
{
if (this.<>1__state == -1)
{
return;
}
this.<sb>5__6 = new StringBuilder();
this.<sb>5__6.AppendLine("Hello world");
<>t__result = this.<sb>5__6.ToString();
}
catch (Exception <>t__ex)
{
this.<>1__state = -1;
this.$builder.SetException(<>t__ex);
return;
}
this.<>1__state = -1;
this.$builder.SetResult(<>t__result);
}
}
[CompilerGenerated]
private sealed class <Start>d__0
{
// Fields
private bool $__disposing;
public AsyncVoidMethodBuilder $builder;
private int <>1__state;
public Program <>4__this;
public Action <>t__MoveNextDelegate;
private TaskAwaiter<string> <a1>t__$await3;
public string <chungles>5__1;
// Methods
[DebuggerHidden]
public <Start>d__0(int <>1__state)
{
this.<>1__state = <>1__state;
}
[DebuggerHidden]
public void Dispose()
{
this.$__disposing = true;
this.MoveNext();
this.<>1__state = -1;
}
public void MoveNext()
{
try
{
string <1>t__$await2;
bool $__doFinallyBodies = true;
if (this.<>1__state != 1)
{
if (this.<>1__state != -1)
{
Console.WriteLine("*** BEFORE CALL ***");
this.<a1>t__$await3 = this.<>4__this.GetString().GetAwaiter<string>();
if (this.<a1>t__$await3.IsCompleted)
{
goto Label_0084;
}
this.<>1__state = 1;
$__doFinallyBodies = false;
this.<a1>t__$await3.OnCompleted(this.<>t__MoveNextDelegate);
}
return;
}
this.<>1__state = 0;
Label_0084:
<1>t__$await2 = this.<a1>t__$await3.GetResult();
this.<a1>t__$await3 = new TaskAwaiter<string>();
this.<chungles>5__1 = <1>t__$await2;
Console.WriteLine("*** AFTER CALL ***");
Console.WriteLine("result = " + this.<chungles>5__1);
Console.ReadLine();
}
catch (Exception <>t__ex)
{
this.<>1__state = -1;
this.$builder.SetException(<>t__ex);
return;
}
this.<>1__state = -1;
this.$builder.SetResult();
}
}
}
What we can also see in there are things like GetAwaiter
which
is a pattern based thing that makes objects awaitable. We will see what the GetAwaiter
does, and how we can make our own objects awaitable shortly, for now you should
be just about to be able to make out that there is some compiler generated code,
and there is a continuation delegate (<>t__MoveNextDelegate
in the reflected code above) that gets called in that reflected code.
One thing that I remember seeing in the Anders Mix Async CTP video was that the Async CTP offers affords responsiveness, while we are awaiting, control is relinquished back to the calling thread.
Catching Exceptions
Demo project name : TryCatchAwaitTask
Catching Exceptions in the Async CTP could not be easier and like I say strongly follows the control flow one would use when running synchronous code, no code smell at all, simple try/catch stuff all the way, here is a small example:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TryCatchAwaitTask
{
class Program
{
static void Main(string[] args)
{
Program p = new Program();
p.DoIt();
}
public async void DoIt()
{
//No problems with this chap
try
{
List<int> results = await
GetSomeNumbers(10,20);
Console.WriteLine("==========START OF GOOD CASE=========");
Parallel.For(0, results.Count, (x) =>
{
Console.WriteLine(x);
});
Console.WriteLine("==========END OF GOOD CASE=========");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
//Make something go wrong
try
{
//simulate a failure by erroring at 5
List<int> results = await GetSomeNumbers(10,5);
Parallel.ForEach(results, (x) =>
{
Console.WriteLine(x);
});
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
Console.ReadLine();
}
/// <summary>
/// Throws InvalidOperationException when index > shouldFailAt value
/// </summary>
public async Task<List<int>> GetSomeNumbers(int upperLimit, int shouldFailAt)
{
List<int> ints = new List<int>();
for (int i = 0; i < upperLimit; i++)
{
if (i > shouldFailAt)
throw new InvalidOperationException(
String.Format("Oh no its > {0}",shouldFailAt));
ints.Add(i);
}
return ints;
}
}
}
And here is what is shown when this is run:
UI Responsiveness
Demo project name : UIResponsiveness
Another area where the Async CTP is very useful is within any area where you still need responsiveness (such as a UI), the following code, simply demonstrates that the user may spawn multiple awaitable bits of code, which are all running but the UI remains responsive, simply open the demo and click the buttons randomly to see what I mean.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading.Tasks;
namespace UIResponsiveness
{
public partial class Form1 : Form
{
Random rand = new Random();
List<string> suffixes = new List<string>() {
"a","b","c","d","e","f","g","h","i","j","k","l",
"m","n","o","p","q","r","s","t","u","v","w","x","y","z"};
public Form1()
{
InitializeComponent();
}
private async void button1_Click(object sender, EventArgs e)
{
RunTaskToGetText();
}
private void button2_Click(object sender, EventArgs e)
{
RunTaskToGetText();
}
private void button3_Click(object sender, EventArgs e)
{
RunTaskToGetText();
}
private async void RunTaskToGetText()
{
List<string> results = await GetSomeText(
suffixes[rand.Next(0,suffixes.Count())], 500000);
textBox1.Text += results[0];
textBox1.Text += results[1];
textBox1.Text += results[results.Count-2];
textBox1.Text += results[results.Count-1];
}
public async Task<List<string>> GetSomeText(string prefix, int upperLimit)
{
List<string> values = new List<string>();
values.Add("=============== New Task kicking off=================\r\n");
for (int i = 0; i < upperLimit; i++)
{
values.Add(String.Format("Value_{0}_{1}\r\n", prefix, i.ToString()));
}
values.Add("=============== New Task Done=================\r\n");
return values;
}
}
}
I can not really show a demo screen shot for this one, but if you try the demo code, you will see it remains reasponsive and results come back when they are finished, and no longer need to be awaited on
Supporting Progress
Demo project name : ProgressReporting
The Async CTP also deals with reporting of progress, which is something that
was not that easy to do in TPL. So how does it do this, well there is a new
interface in the Async CTP called IProgress<T>
which has been
implemented for you in the CTP by the Progress<T>
class. Where
these look like this:
public interface IProgress<in T>
{
void Report(T value);
}
public class Progress<T> : IProgress<T>
{
public Progress();
public Progress(Action<T> handler);
public event ProgressEventHandler<T> ProgressChanged;
protected virtual void OnReport(T value);
}
So how do we use the Progress<T>
in our own code, well its pretty simple, here is a simple example, which simply
writes the current progress value out to the Console. I should point out that working out the correct progress value is up to you to come up with
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ProgressReporting
{
class Program
{
static void Main(string[] args)
{
Program p = new Program();
p.DoIt();
}
public async void DoIt()
{
Progress<int> progress = new Progress<int>();
progress.ProgressChanged += (sender, e) =>
{
Console.WriteLine(String.Format("Progress has seen {0} item", e));
};
List<int> results = await GetSomeNumbers(10, progress);
Console.WriteLine("Task results are");
Parallel.For(0, results.Count, (x) =>
{
Console.WriteLine(x);
});
Console.ReadLine();
}
public async Task<List<int>> GetSomeNumbers(
int upperLimit, IProgress<int> progress)
{
List<int> ints = new List<int>();
for (int i = 0; i < upperLimit; i++)
{
ints.Add(i);
progress.Report(i + 1);
}
return ints;
}
}
}
And here is an example of it running:
Awaiter/GetAwaiter
So now that we have seen some examples of the new Async CTP key words in action, lets take a deeper look at just what it means to be awaitable.
The Await
key word CAN ONLY be used with something that is awaiatble. Thanks
to the Async CTP Task
has been extended to be awaitable. So how does it do that
exactly, and can we make our own types awaitable.
Well yes we can, as it turns out all you need to do is ensure that certain core methods that are expected are present, which can be instance based or extension methods.
The things you must implement to make your own objects awaiatble or extend an existing object are as follows:
public void GetResult()
public void OnCompleted(Action continuation)
public bool IsCompleted { get; set; }
Which will be expected when the compiler generated code that is created in response to using the new
Await
keyword is used, which internally simply calls the
GetAwaiter()
method in the compiler re-written code, so as long as your
object has these methods/property you are awaitable.
Awaiter With Return Value
Demo project name : AwaiterThatReturnsSomething
So now that we know how to make a custom awaitable object lets try it out, by doing something trivial like adding the ability to wait for a double to be raised to the power of something you specify (which is obviously no use for anything other than a demo, but you get the idea from it I would hope).
So we start with creating an extension method on Double as follows, which as you can see returns a DoubleAwaiter
public static class DoubleExtensionMethods
{
public static DoubleAwaiter GetAwaiter(this double demoDouble, int power)
{
return new DoubleAwaiter(demoDouble, power);
}
}
Where the DoubleAwaiter
looks like this, where the most important parts are that we set the IsCompleted
we call the continuation Action
public class DoubleAwaiter
{
private double theValue;
private int power;
public DoubleAwaiter(double theValue, int power)
{
this.theValue = theValue;
this.power = power;
IsCompleted = false;
}
public DoubleAwaiter GetAwaiter()
{
return this;
}
public double GetResult()
{
return theValue;
}
public void OnCompleted(Action continuation)
{
this.theValue = Math.Pow(theValue, power);
IsCompleted = true;
continuation();
}
public bool IsCompleted { get; set; }
}
Which we can now use like this
class Program
{
static void Main(string[] args)
{
ThreadPool.QueueUserWorkItem(async delegate
{
double x = 10;
double x2 = await x.GetAwaiter(3);
Console.WriteLine(string.Format("x was : {0}, x2 is {1}",x,x2));
});
Console.ReadLine();
}
}
Which when run produces the following output (like I say this is about the dumbest use of a custom awaitable you could imagine it is purely there to show you the structure you need to need to use if you want to make you own code awaitable)
Synchronization Awaiter
Demo project name : SyncContextControlAwaiter
Since the concept of making something awaitable really boils down to implementing the correct couple of methods/properties we can exploit this to do some pretty wild stuff, for example you must have come up against the need to invoke a cross thread call back to the UI thread when dealing with UI code.
Well we can actually use a custom awaiter that will make this whole process a lot neater, consider these portions of Windows Forms code:
public static class ControlExtensionMethods
{
public static ControlAwaiter GetAwaiter(this Control control)
{
return new ControlAwaiter(control);
}
}
public class ControlAwaiter
{
private readonly Control control;
public ControlAwaiter(Control control)
{
if (control == null)
throw new ArgumentNullException("control");
this.control = control;
IsCompleted = false;
}
public void GetResult()
{
}
public void OnCompleted(Action continuation)
{
control.BeginInvoke(continuation);
IsCompleted = true;
}
public bool IsCompleted { get; set; }
}
Where we are using the custom awaiter to do the marshalling back to the UI thread for the Windows Forms app using BeginInvoke(..)
, where
we had this calling code:
private void BtnSyncContextAwaiter_Click(object sender, EventArgs e)
{
ThreadPool.QueueUserWorkItem(async delegate
{
string text = "This should work just fine thanks to our lovely \"ControlAwaiter\" " +
"which ensures correct thread marshalling";
await textBox1;
textBox1.Text = text;
});
}
The demo demonstrates using the custom synchronization context awaiter shown above.
The demo code also demonstrates what the code would do when we don't use the custom synchronization context awaiter shown above, which uses this code
private void BtnNoAwaiter_Click(object sender, EventArgs e)
{
ThreadPool.QueueUserWorkItem(delegate
{
try
{
string text = "This should cause a problem, as we have spawned " +
"background thread using ThreadPool" +
"Which is not the correct thread to change the UI control " +
"so should cause a CrossThread Violation";
textBox1.Text = text;
}
catch (InvalidOperationException ioex)
{
MessageBox.Show(ioex.Message);
}
});
}
Here is a screen shot of the demo code using the custom synchronization context awaiter shown above
And here is a screen shot of the demo code NOT using the custom synchronization context awaiter shown above
Realistic Awaiter
Demo project name : MoreRealisticAwaiterWithCancellation
Now that you have seen that you can create your own awaitable code and even create some pretty novel uses of it, I think you should know that most of the time you will be using Task(s) as what you Await on. So this last example illustrates a fuller example that uses Task(s) and also shows you how you might go about Unit testing some Task centric service code that you are awaiting on, that is constructed in a manner that we could easily inject an alternative service other than Task centric service code.
This demo also demonstrates how to use a CancellationToken to cancel a awaitable Task.
So lets start with the actual code that does the awaiting, which looks like this:
class Program
{
static void Main(string[] args)
{
ManualResetEvent mre1 = new ManualResetEvent(true);
ManualResetEvent mre2 = new ManualResetEvent(false);
ManualResetEvent mre3 = new ManualResetEvent(false);
ManualResetEvent mre4 = new ManualResetEvent(false);
DoItForReal(false, mre1, mre2);
DoItForReal(true, mre2, mre3);
Console.ReadLine();
}
/// <summary>
/// Shows how you can await on a Task based service
/// </summary>
private static async void DoItForReal(
bool shouldCancel, ManualResetEvent mreIn, ManualResetEvent mreOut)
{
mreIn.WaitOne();
CancellationTokenSource cts = new CancellationTokenSource();
int upperLimit = 50;
int cancelAfter = (int)upperLimit / 5;
// which is what is used in WorkProvider class
int waitDelayForEachDataItem = 10;
//allows some items to be processed before cancelling
if (shouldCancel)
cts.CancelAfter(cancelAfter * waitDelayForEachDataItem);
Console.WriteLine();
Console.WriteLine("=========================================");
Console.WriteLine("Started DoItForReal()");
try
{
List<String> data = await new Worker(new WorkProvider(),
upperLimit, cts.Token).GetData();
foreach (String item in data)
{
Console.WriteLine(item);
}
//allow those waiting on this WaitHandle to continue
if (mreOut != null) { mreOut.Set(); }
}
catch (OperationCanceledException)
{
Console.WriteLine("Processing canceled.");
//allow those waiting on this WaitHandle to continue
if (mreOut != null) { mreOut.Set(); }
}
catch (AggregateException aggEx)
{
Console.WriteLine("AggEx caught");
//allow those waiting on this WaitHandle to continue
if (mreOut != null) { mreOut.Set(); }
}
finally
{
Console.WriteLine("Finished DoItForReal()");
Console.WriteLine("=========================================");
Console.WriteLine();
}
}
}
The use of the ManualResetEvent
(s) are there just to ensure that one scenario finishes printing to the console before the other scenario starts, just to keep the
demo print out screen shot clear for you readers
So nows lets examine the Worker
code that the code above Await
s on
public class Worker
{
private IWorkProvider workprovider;
private int upperLimit;
private CancellationToken token;
public Worker(IWorkProvider workprovider, int upperLimit, CancellationToken token)
{
this.workprovider = workprovider;
this.upperLimit = upperLimit;
this.token = token;
}
public Task<List<String>> GetData()
{
return workprovider.GetData(upperLimit, token);
}
}
It can be seen that design supports an alternative IWorkProvider
being supplied either from a Unit test or maybe by the use
of an abstract factory that is being used in conjunction with a IOC container to resolve the actual Worker
instance.
The IWorkProvider
interface in the demo app looks like
this:
public interface IWorkProvider
{
Task<List<String>> GetData(int upperLimit, CancellationToken token);
}
The actual code that is being awaited on is the Task<List<String>>
that is returned from the GetData()
method.
Lets now have a look at the actual Task
based service code that provides the Task<List<String>>
public class WorkProvider : IWorkProvider
{
public Task<List<string>> GetData(int upperLimit, System.Threading.CancellationToken token)
{
//will not be TaskEx when CTP is in .NET 5.0 Framework
return TaskEx.Run(() =>
{
List<string> results = new List<string>();
for (int i = 0; i < upperLimit; i++)
{
token.ThrowIfCancellationRequested();
Thread.Sleep(10);
results.Add(string.Format("Added runtime string {0}",i.ToString()));
}
return results;
});
}
}
Now when we run the actual code that uses this Task
based service code we get the following, where we run it once to completion, and once where we expect it to be cancelled as the demo code initiated a cancel via
a CancellationTokenSource
Now you may be asking yourself, well that's great I have some Task
based service code, which we can Await
on, but how the heck am I supposed to be
able to Unit test that bad boy. Well although the demo app does not include a
formal Unit test, it provides all the peices to the puzzle, such as
- Seperation of concern
- Extension point for IOC to supply an alternative
IWorkProvider
implementation, or for a Unit test to do so. - Mocking of the
IWorkProvider
which shows you how you can mock yourTask
(s)
I prefer to use Moq (cool mocking framework) to carry out any test code that I do, so
here is an example of how you might write a Unit test based mocked service that
will act the same as the Task
based service code, we just saw
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using Moq;
using System.Threading.Tasks;
namespace MoreRealisticAwaiterWithCancellation
{
class Program
{
static void Main(string[] args)
{
...
...
...
DoItUsingMoq_YouKnowForUnitTestsLike(mre3);
Console.ReadLine();
}
/// <summary>
/// Shows how you might mock a task based service using "Moq" and TaskCompletionSource
/// </summary>
private static async void DoItUsingMoq_YouKnowForUnitTestsLike(ManualResetEvent mreIn)
{
mreIn.WaitOne();
CancellationTokenSource cts = new CancellationTokenSource();
int upperLimit = 50;
List<String> dummyResults = new List<string>();
for (int i = 0; i < upperLimit; i++)
{
dummyResults.Add(String.Format("Dummy Result {0}", i.ToString()));
}
//Allows this test method to simulate a Task with a result without actually creating a Task
TaskCompletionSource<List<String>> tcs =
new TaskCompletionSource<List<String>>();
tcs.SetResult(dummyResults);
Console.WriteLine();
Console.WriteLine("=========================================");
Console.WriteLine("Started DoItUsingMoq_YouKnowForUnitTestsLike()");
try
{
Mock<IWorkProvider> mockWorkProvider = new Mock<IWorkProvider>();
mockWorkProvider
.Setup(x => x.GetData(
It.IsAny<Int32>(),
It.IsAny<CancellationToken>()))
.Returns(tcs.Task);
List<String> data = await new Worker(
mockWorkProvider.Object, upperLimit, cts.Token).GetData();
foreach (String item in data)
{
Console.WriteLine(item);
}
}
finally
{
Console.WriteLine("Finished DoItUsingMoq_YouKnowForUnitTestsLike()");
Console.WriteLine("=========================================");
Console.WriteLine();
}
}
}
}
It can be seen that this example uses Moq to create a
mock IWorkProvider
which is supplied to the Worker
constructor which is what we will end up waiting on. So if we are in a Unit test
and not actually running a Task
based service, how are we ever
going to run the awaiting continuation. Well the answer lies at the beginning of
this article, we simply use a TaskCompletionSource
to simulate a
Task.Result
by using the TaskCompletionSource.SetResult(..)
method and supplying the TaskCompletionSource.Task
to the
mock object that will be used in this test code.
As always here is an example of this running.
I did not do anything with the CancellationToken
here, but this could be
accomplished if you really wanted to I feel. Moq is very cool
and would be more than able to do the job I think, I was just tired by this
point, sorry.
That's It For Now
That is all I wanted to say in this in this article. I hope you liked it, and have enjoyed the TPL ride, I know it has been a long ride, and too be honest there have been times when I thought I would never finish it off, it is finally done. So I ask if you did like this article, could you please spare some time to leave a comment and a vote. Many thanks.
Post Comment
jQh5tS There as certainly a lot to find out about this subject. I really like all the points you made.
Your website is very cool, i have bookmarked it.
dgdCZ0 What's Happening i'm new to this, I stumbled upon this I have found It positively useful and it has aided me out loads. I hope to contribute & help other users like its aided me. Good job.
ykdRYH Hi, i think that i saw you visited my weblog so i came to ���return the favor���.I'm trying to find things to improve my site!I suppose its ok to use some of your ideas!!