Introduction
This is the sixth 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 advanced 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 6 of 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 Partitioner / Aggregate Operations
- Parallel LINQ
- Pipelines
- Advanced Scenarios / v.Next for Tasks (this article)
Table of Contents
Prerequisites
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:
Finishing up TPL
This small section will hopefully finish up the few last remaining bits and pieces that I still wanted to go through with TPL as it stands right now; you know with the classes you have available 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 TPL's inbuilt FromAsync
which expects to create a Task based on the old familiar Begin/End methods that worked with the 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 reference to, which also supports being called asynchronously; 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 straightforward; we simply 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 MSDN's 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 first 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
{
class Program
{
static void Main(string[] args)
{
TaskCompletionSource<String> tcs1 =
new TaskCompletionSource<String>();
Task<String> task1 = tcs1.Task;
Task.Factory.StartNew(() =>
{
Thread.Sleep(1000);
tcs1.SetResult("Task1 Completed");
});
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;
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 second example illustrates 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 second CTP release, and a few things have changed (which I will not be mentioning in this article). This article will concentrate on what is allowable right now using the latest Async CTP (which is 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, right? Well, to be honest, yes, but all is not lost, far from it. The Async CTP simply builds upon Task
s/TPL in a very elegant manner. It does this by introducing two new keywords 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 keywords 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 change to the program flow, no nasty call this callback for error, call this callback for success, not an IAsyncResult
in sight. And to be honest, to convert a synchronous method into an asynchronous method would be very, very easy to do using Async CTP.
We will see more of these key ords as we progress, so let's carry on, shall we?
Our First Simple Async Example
Demo project name: SimpleAsync
Let's start with an insanely trivial example, shall we? Here is the code. Note the highlighted Async/Await
keywords there, and also note that the 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 is going on here? We have a Start()
method that is marked up with the new Async
keyword, which tells the compiler that this method will be run asynchronously, and then we have a String
which is returned via the GetString()
method that really returns a Task<String>
that we are awaiting on using the new Await
keyword, 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 knows what to emit when it sees an Async
keyword being used. The next piece to the puzzle is that Task
has been vamped up to become an Awaitable object (more on this later), and thanks also to the CTP, 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 of 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
{
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();
}
[CompilerGenerated]
private sealed class <GetString>d__5
{
private bool $__disposing;
public AsyncTaskMethodBuilder<string> $builder;
private int <>1__state;
public Program <>4__this;
public Action <>t__MoveNextDelegate;
public StringBuilder <sb>5__6;
[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
{
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;
[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 just about 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 Async CTP offers responsiveness; while we are awaiting, the control is relinquished back to the calling thread.
Catching Exceptions
Demo project name: TryCatchAwaitTask
Catching exceptions in 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()
{
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);
}
try
{
List<int> results = await GetSomeNumbers(10,5);
Parallel.ForEach(results, (x) =>
{
Console.WriteLine(x);
});
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
Console.ReadLine();
}
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 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 screenshot for this one, but if you try the demo code, you will see it remains responsive 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 Async CTP called IProgress<T>
which has been implemented for you in the CTP by the Progress<T>
class. 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 Progress<T>
in our own code? Well, it's pretty simple. Here is a simple example which 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 keywords in action, let's take a deeper look at just what it means to be awaitable.
The Await
keyword can only be used with something that is awaiatble. Thanks to 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, let's 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
and 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 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 callback 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 screenshot of the demo code using the custom synchronization context awaiter shown above:
And here is a screenshot 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 an awaitable Task.
Let's 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();
}
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;
int waitDelayForEachDataItem = 10;
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);
}
if (mreOut != null) { mreOut.Set(); }
}
catch (OperationCanceledException)
{
Console.WriteLine("Processing canceled.");
if (mreOut != null) { mreOut.Set(); }
}
catch (AggregateException aggEx)
{
Console.WriteLine("AggEx caught");
if (mreOut != null) { mreOut.Set(); }
}
finally
{
Console.WriteLine("Finished DoItForReal()");
Console.WriteLine("=========================================");
Console.WriteLine();
}
}
}
The use of 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 screenshot clear for you readers.
Now let's 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 the 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 an 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. Let's 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)
{
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 pieces 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
IWorkProvider
which shows you how you can mock your Task
(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 that 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();
}
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()));
}
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 article. I hope you liked it and have enjoyed the TPL ride. I know it has been a long ride, and to 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.