Introduction
Why I am Writing this Article
A lot of tutorials have already been written about TPL and the new .NET 4.5 async-await features, the
most prominent examples are Task Parallel Library: 1 of n, Task Parallel Library: 2 of n, Task Parallel Library: 6 of n and
Threading in C#, PART 5: PARALLEL PROGRAMMING.
Here I present my own version of a TPL and async-await tutorial demonstrating why
TPL and async-await functionality is so useful and how to apply it to some
well-known problems.
The Purpose of TPL and async-await Features
TPL and async-await features simplify dealing with asynchronous calls. There maybe several
reasons to have asynchronous calls in your code.
Most software products, do not act in a vacuum - they has to contact other
systems. The common examples of the need for such communications are
UI or web client contacting the server to read or update some data or the middleware contacting the
databases, etc.
When such communications occur, the software on the client side cannot control or predict the speed of the
response from the server. If the client sends a synchronous request to the server, the calling thread
blocks until the server response is obtained and processed. Such blockage can lead to e.g. GUI or
web application freezing completely for an unspecified period of time - which is definitely not
desirable.
On the positive side, however, the synchronous calls, preserve the program structure - calling
the server synchronously is as easy (from the developer's point of view) as making a local call.
We can also solve the communication problem by using asynchronous communications with the server.
Most common way to do it, is to register a callback on the client to fire when the server
returns information, then to send an asynchronous request to the server - a request that does not block
the current thread. After the server responds, the callback code will update the client
based on the information obtained from the server.
The problem with this approach is that the flow of the code is broken into two different pieces -
the code before the call to the server and the callback code that runs after the server responds.
In other words - async calls to the server dramatically change the structure of the code. When you
need to make only one call to the server at a time, you can still handle the situation somehow at the
expense of making the code a little worse - having two functions per call as described above,
but what if you need to have multiple calls to the server - some running in parallel, some sequential,
some have to be executed only after a number of previous asynchronous calls have completed. Asynchronous
programming in that case can easily lead to a developer's nightmare - more and more callbacks will have to be
inserted for every async call breaking the program into a bunch of pieces that have to be connected logically. Also
some of the callbacks will have to be synchronized in order to detect their completion before starting some other
processing.
There can be reasons other than calling the server for making asynchronous calls, e.g. you might want to
do some processing on different threads in order to better utilize the multiple cores on your machine
or because such processing has been built into the framework that you are using.
The purpose of TPL and the async-await features is to make the asynchronous programming almost as simple
as the synchronous programming, allowing the developers to preserve the logical structure of the code
even when it executes asynchronously.
Organization of the Article
First I provide a refresher on TPL and async-await features. Then I show how to apply them to problems
you are likely encounter - in particular to making calls to web services, scheduling mutltiple
background workers and animation storyboards.
Software Tools Used
We use Visual Studio 2012 and .NET 4.5 for creating samples for this article. The TPL features
(without async-await and some other .NET 4.5 features) can be all run
under .NET 4.0 within Visual Studio 2010.
Refresher on the TPL
For the full coverage of the TPL features, please, see the links we provided at the beginning
of the article - here we only describe the features that we consider most useful.
Tasks
TPL's basic unit of code is Task
. Task
s can run, can be waited for to finish.
Tasks can have child tasks. The code can wait on multiple tasks to finish before it proceeds.
Tasks can be cancelled and their progress can be monitored.
Task
can only run from start to finish once - you cannot run the same task object two times. If you
need to re-run the Task
, you'll need to create another Task
object
for running the same code.
By default the tasks run on a thread from a thread pool. TaskScheduler
allows the developer
to customize the thread for running the task. Some tasks, that use TaskCompletionSource
do not spawn a separate thread.
Simple Task Sample
The simplest sample showing the basic task features is located under TaskSample solution. Here is its code:
Task t1 = new Task
(
() =>
{
Thread.Sleep(1000);
Console.WriteLine("Task Completed");
}
);
t1.Start();
t1.Wait();
Console.WriteLine("End of Main");
Console.ReadLine();
Pretty self-explanatory - we define the task to sleep for 1 second and then to print
"Task Completed" on the console. We start the task after we define it, print
"We are waiting for the task to finish" and then call Wait
method to block the
main program until the task finishes.
Simple Task Throwing Exception
If the task throws an exception, such exception can be caught in the main thread by surrounding
Wait()
method with try
-catch
clause, as shown under
TaskWithException project:
Task t1 = new Task
(
() =>
{
Thread.Sleep(1000);
throw new Exception("This is an Exception within a task");
Console.WriteLine("Task Completed");
}
);
t1.Start();
Console.WriteLine("We are waiting for the task to finish");
try
{
t1.Wait();
}
catch (AggregateException e)
{
Console.WriteLine("Caught Exception " + e.InnerException.Message);
}
Console.WriteLine("End of Main");
Console.ReadLine();
Waiting with Timeout
Function Wait()
has a version that takes timeout parameter. Such version returns
bool
value set to false
when the timeout occurred and true
otherwise. The corresponding sample is located under TaskWithTimeout project:
Task t1 = new Task
(
() =>
{
Thread.Sleep(4000);
Console.WriteLine("Task Completed");
}
);
t1.Start();
Console.WriteLine("We are waiting for the task to finish");
bool hasNotTimedOut = t1.Wait(2000);
if (hasNotTimedOut)
{
Console.WriteLine("The task has not timed out");
}
else
{
Console.WriteLine("The task has timed out");
}
Console.WriteLine("End of Main");
Console.ReadLine();
The main program waits for two seconds for the task to finish and then continues.
Note that the task is not cancelled in this case - it continues after the sleep period
and "Task Completed" message is printed at the very end - after the "End of Main" message.
Cancelling a Task
Project TaskCancellation shows how to cancel a task:
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
CancellationToken cancellationToken = cancellationTokenSource.Token;
Task t1 = new Task
(
() =>
{
int i = 0;
while (true) {
i++;
Thread.Sleep(1000);
Console.WriteLine("This is task iteration " + i);
cancellationToken.ThrowIfCancellationRequested();
}
},
cancellationToken );
t1.Start();
Thread.Sleep(3000);
cancellationTokenSource.Cancel();
try
{
t1.Wait();
}
catch (AggregateException e)
{
if (e.InnerException is OperationCanceledException)
{
Console.WriteLine("The operation has been cancelled");
}
else
{
Console.WriteLine("Some unexpected exception");
}
}
Console.WriteLine("End of Main");
Console.ReadLine();
First, we create CancellationTokenSource
. Its property Token
contains a CancellationToken
object. This object is passed to the Task
's
constructor.
After starting the Task
, we wait 3 seconds and call Cancel()
method on the CancellationTokenSource
. Within the task, we call
cancellationToken.ThrowIfCancellationRequested()
method at the end of each
iteration. This method throws an OperationCanceledException
if cancellation
was requested, otherwise, it does nothing. When
OperationCanceledException
propagates outside of the Task
's body,
it is wrapped
within AggregateException
- and this is the exception that we catch within Main
.
Tasks with Child Tasks
You can start a Task
within another Task
's body. In
that case, Task
started inside another Task
's body is called
the child Task
and the Task
in whose body the child Task
starts is called the parent Task
.
A Task
constructor has an override that accepts TaskCreationOptions
argument. When this argument is specified to include TaskCreationOptions.AttachedToParent
option, the created child Task
becomes "attached" to its parent. This "attachment" creates
very useful dependencies between the parent and the child tasks in that:
- The parent
Task
won't complete until all of its attached child Task
s have completed.
-
The parent-child
Task
s create a hierarchy very similar to function stack - only here we have
child Task
s possibly executing in different threads. We call it "attached child stack".
If an attached child throws an exception, the exception will be propagated up the
"attached child stack" - wrapped by another
AggregateException
at each step of its propagation.
It can be caught at any level within the "attached child stack"
A Task
created within a parent Task
's body without AttachedToParent
flag is called "detached". There are no dependencies between a parent Task
and
its "detached" child Task
s and we are not very interested in "detached" child
Task
s.
TaskWithChildrent project presents the attached child Task
functionality:
Task task = new Task
(
() =>
{
Task childTask = new Task
(
() =>
{
Thread.Sleep(1000);
Task grandChildTask = new Task
(
() =>
{
Thread.Sleep(2000);
throw new Exception("Grandchild exception");
},
TaskCreationOptions.AttachedToParent
);
grandChildTask.Start();
},
TaskCreationOptions.AttachedToParent
);
childTask.Start();
}
);
task.Start();
try
{
task.Wait();
Console.WriteLine("The task is finished");
}
catch (AggregateException e)
{
Console.WriteLine("Exception caught: " + e.InnerException.InnerException.InnerException.Message);
}
Console.WriteLine("End of Main");
Console.ReadLine();
An exception is thrown at the grand-child level and caught at the top level within
Main
function. As was mentioned about, it is wrapped by a new
AggregateException
as it passes each level within the "attached child stack",
so, in order to get to the original exception, we need to use InnerException
property several times: e.InnerException.InnerException.InnerException.Message
.
Tasks that Return Result
If you want a Task
to product some result retrieavable from the Task
itself, you can use Task<TResult>
generic form of it. The generic argument
TResult
specifies the type of the Task
's result. Everything discussed above
still applies to such tasks. The only extra functionality available from such Task
s is
the property
TResult Task<TResult>.Result;
It blocks while the Task
is running (just like Wait()
method does) and it returns the
result after the Task
's run is completed.
Task.WhenAll() and Task.WhenAny()
In .NET 4.5, there is an important static method on
Task
class - Task.WhenAll()
.
It returns a Task
object that completes when and only when all the
Task
objects passed to it are completed.
There is a number of method overrides - some versions of the method
accept a parameter list - others an array of Task
objects.
If the input Tasks
return results, the Task
returned by Task.WhenAll()
method can return an array of values
corresponding to the results from the individual input Task
objects.
Another method Task.WhenAny()
creates a Task
that waits only for one Task
to complete out of many. It is used less often,
but can be quite useful in several scenarios.
There are also methods that can block the current thread's execution until a number of Task
s
is finished of one Task
out of the number is finished without creating new Task
s.
These methods are Task.WaitAll(...)
and Task.WaitAny(...)
ContinueWith Method
There is a Task
method ContinueWith
that allows to specify functionality
you want to be executed after the task is finished - a kind of a callback to the Task
.
Code for testing this method is located under ContinueWithTest project.
Task t1 = new Task
(
() =>
{
Thread.Sleep(1000);
throw new Exception("this is an exception");
Console.WriteLine("This is T1");
}
);
Task t2 = t1.ContinueWith
(
(predecessorTask) =>
{
if (predecessorTask.Exception != null)
{
Console.WriteLine("Predecessor Exception within Continuation");
return;
}
Thread.Sleep(1000);
Console.WriteLine("This is Continuation");
},
TaskContinuationOptions.AttachedToParent | TaskContinuationOptions.OnlyOnRanToCompletion
);
t1.Start();
try
{
t1.Wait();
t2.Wait();
}
catch (AggregateException e)
{
Console.WriteLine(e.InnerException.Message);
}
We create Task
t2
to be the continuation of the Task
t1
.
By passing TaskContinuationOptions.OnlyOnRanToCompletion
option we make the continuation only run in case the predecessor Task
ran successfully. Since in our case, the predecessor Task
threw an exception,
the successor Task
will never be called.
ContinueWhenAll and ContinueWhenAny
Two methods can be used to create continuation Task
s to start after a
number of predecessor Task
s are completed: Task.Factory.ContinueWhenAll(...)
and Task.Factory.ContinueWhenAny(...)
. In fact these two methods are very similar to
the methods WhenAll
and WhenAny
described above, only they were part of
.NET 4.0 while WhenAll
and WhenAny
were added in .NET 4.5.
Task Progress
.NET 4.5 added IProgress<T>
interface and Progress<T>
class to
facilitate measuring the progress of a Task
or any other code. Both
accept a generic type parameter T
to specify the type of the value that measures progress.
Progress<T>
implicitely implements IProgress<T>
interface.
IProgress<T>
has method Report(T )
that should be called to report
progress. Progress<T>
has an event ProgressChanged
that
fires every time the progress is reported. Second argument to this event is the progress value.
An event handler can be attached to this event to handle the progress reporting.
TaskProgressTest
solution gives a usage example for progress functionality:
Progress<string> progress = new Progress<string>();
Task t = new Task
(
() =>
{
for (int i = 0; i < 5; i++)
{
Thread.Sleep(1000);
((IProgress<string>)progress).Report("" + i);
}
}
);
progress.ProgressChanged += (sender, str) =>
{
Console.WriteLine("Progress is " + str);
};
t.Start();
t.Wait();
The Task
t
iterates 5 times and uses the iteration
index number i
as the progress indicator (this number is passed
to IProgress<string>.Report(...)
function).
ProgressChanged
event handler prints the reported progress to the console.
TaskCompletionSource and Wrapping Event Based Functionality within a Task
A lot of asynchronous functionality coming as part of .NET or from some 3-rd parties is built based on
the Event-based Asynchronous Pattern (or EAP). The principle of the EAP
is that the async functionality provides
a method to start the asynchronous operation and a callback event that allows attaching a callback event
handler that will be called once the asynchronous operation completes. The client code first attaches the
callback handler to the async functionality and then calls the method to start the async operation.
Examples of such EAP functionality are BackgroundWorker
, asynchronous service references
in .NET 4.0 etc.
Much of .NET 4.5 functionality has been changed to Task
s and BackgroundWorker
is
all but obsolete now, but you still might come across some
instances when it is necessary to use the EAP pattern.
In such cases, it will be nice to produce Task
objects out of the EAP functionality so that
you'll be able to schedule them to run in parallel or wait for a number of previous Task
s to
finish etc. In this section we show how to achieve it.
We use TaskCompletionSource
class to produce Task
s that do not create their own
threading and instead use other object's asynchronous functionality.
The EAP functionality is located under MyEAPTest class. It provides function StartAsync()
that runs a method in a separate thread printing a string that is passed to it. At the end of the async
method, EAPTestCompleted
event is fired:
public class MyEAPTest
{
Thread _thread;
public event Action EAPTestCompleted;
public MyEAPTest(string stringToPrint)
{
_thread = new Thread
(
(ThreadStart) (() =>
{
Thread.Sleep(1000); Console.WriteLine(stringToPrint); if (EAPTestCompleted != null)
EAPTestCompleted();
})
);
}
public void StartAsync()
{
_thread.Start();
}
}
EPAToTaskUtils
class provides ToTask()
static
extension method converting the MyEAPTest
object into a Task
object:
public static Task<object> ToTask(this MyEAPTest epaTest)
{
TaskCompletionSource<object> taskCompletionSource =
new TaskCompletionSource<object>(TaskCreationOptions.AttachedToParent);
Action onEpaTestCompleted = null;
onEpaTestCompleted = () =>
{
epaTest.EAPTestCompleted -= onEpaTestCompleted;
taskCompletionSource.SetResult(null);
};
epaTest.EAPTestCompleted += onEpaTestCompleted;
epaTest.StartAsync();
return taskCompletionSource.Task;
}
Note that ToTask()
method described above not only creates a Task
but also starts it. In fact, it is not recommended that you create a Task
out of EAP
without starting it, since the TaskCompletionSource
's Task
returned
will not have a valid Start
method implementation, which will be confusing for the
users of the functionality. The fact that the Task
starts
within the conversion ToTask
method, does not limit the use of the functionality, however, since we can always postpone
calling the ToTask
until we are ready to start the Task
.
Calling SetResult
method on TaskCompletionSource
will cause its
Task
to stop blocking.
You can also use TaskCompletionSource.SetCanceled()
method to throw
OperationCancelledException
from the blocking methods or, you can use
TaskCompletionSource.SetException(...)
method that will make the blocking method
of the TaskCompletionSource
's Task
to behave as if an exception was
thrown during the Task
execution. In particular, if the TaskCompletionSource
was created with AttachedToParent
creation option, the exception will propagate up the
"attached child stack".
Here is the code that creates and schedules the Task
s:
MyEAPTest epaTest1 = new MyEAPTest("this is EPA test1");
MyEAPTest epaTest2 = new MyEAPTest("this is EPA test2");
MyEAPTest epaTest3 = new MyEAPTest("this is EPA test3");
Task<object> t1 = epaTest1.ToTask();
Task<object> t2 = epaTest2.ToTask();
Task.WaitAll(t1, t2);
Task<object> t3 = epaTest3.ToTask();
t3.Wait();
Task
s t1
and t2
are run in parallel. Task
t3
is scheduled to run only after t1
and t2
have completed.
The scheduling effect above would be much more difficult to achieve without the TPL functionality.
New .NET 4.5 async and await Features
.NET 4.5 async
and await
features simplify the asynchronous
programming even further. They basically allow
writing and debugging an async program almost as if it is a usual synchronous program.
In order to run the samples from this section, you need Visual Studio 2012.
Simple async-await Test
Take a look at Program.cs file under AwaitTest program:
static void Main(string[] args)
{
RunIt();
Console.WriteLine("End of Main");
Console.ReadLine();
}
static async void RunIt()
{
Task t = new Task
(
() =>
{
Thread.Sleep(3000);
Console.WriteLine("Ended the task");
}
);
t.Start();
await t;
Console.WriteLine("After await");
}
Notice that there is a method RunIt()
called from Main()
. Why can't we test await
keyword within the Main()
method itself? The reason is that any method that has await
keyword in it should be declared
with async
keyword as a method that possibly introduces some asynchronous processing, while Main()
is a special method that cannot be marked with async
keyword.
Note, that when you run the code, the string "End of Main" is printed before "Ended the task".
This means that during the "waiting" period, the main thread is not blocked - it continues and would
have completed the program if it was not for the Console.ReadLine()
call
at the end of the Main()
method. This is a major difference between await
and Task.Wait()
method. Indeed, change await t;
line to t.Wait()
and remove async
keyword from RunIt
method's declaration. After that, when you
run the code, first "Ended the task" message will be printed and only later "End of Main" - meaning that
t.Wait()
is blocking the main thread until the Task
is completed.
The execution context assigns a thread
for the code placed after await
keyword to be executed once waiting is completed.
Note that we can change the return type of RunIt()
method to Task
without returning anything from the method itself. Compiler will generate the Task
object
for the method itself, since the method is declared with async
keyword. The Main
method can get the task and wait on it:
static void Main(string[] args)
{
Task t = RunIt();
t.Wait();
Console.WriteLine("End of Main");
Console.ReadLine();
}
static async Task RunIt()
{
Task t = new Task
(
() =>
{
Thread.Sleep(3000);
Console.WriteLine("Ended the task");
}
);
t.Start();
await t;
Console.WriteLine("After await");
}
Because of calling Wait()
method on the Task
object
returned by RunIt()
method, the Main()
method
blocks until asynchronous processing is completed within RunIt()
and
"End of Main" string is printed after "Ended the Task".
Async-Await Tests with Return Values
Code for AwaitWithReturnsTest project is very similar to that considered above,
except that the Task
that we are waiting for, returns a string result
and correspondingly we use string result = await t;
to assing the
result of the Task
to a string:
static void Main(string[] args)
{
Task<string> runItTask = RunIt();
Console.WriteLine("End of Main");
Console.ReadLine();
}
static async Task<string> RunIt()
{
Task<string> t = new Task<string>
(
() =>
{
Thread.Sleep(3000);
Console.WriteLine("Ended the task");
return "This is task";
}
);
t.Start();
string result = await t;
Console.WriteLine("After await, result = '" + result + "'");
return result;
}
Note, that our RunIt()
function returns result
of type string,
while it is decrlared to return a Task<string>
. The change of the return
type happens due to the async
keyword in front of the method declaration,
which wraps whatever is returned by the function within a Task
.
The returned Task<string>
can be used to block the Main()
thread execution until the Task
finishes or to use await
keyword on it higher up the function stack.
Async-Await and Exceptions
Take a look at AWaitWithException project. File Program.cs is almost the same as that of our AwaitTest project
except that there is an exception thrown within the Task
. This exception is caught
by the try-catch
block around await t;
line and the exception message is printed:
static async void RunIt()
{
Task t = new Task
(
() =>
{
Thread.Sleep(3000);
throw new Exception("This is an exception thrown from a Task");
Console.WriteLine("Ended the task");
}
);
t.Start();
try
{
await t;
}
catch (Exception e)
{
Console.WriteLine("Caught Exception '" + e.Message + "'");
}
Console.WriteLine("After await");
}
Note that await
keyword unwraps one level of the AggregateException
so that
in the case above, we catch the original Exception
and not the AggregateException
object. This is another way in which async programming becomes more like the usual synchronous one.
Usage Patterns for Task and Async-Await Functionality
In this section I describe several important cases when the TPL and async-await functionality comes very handy.
Remote Service Calls using .NET 4.5
Most financial applications collect data from many different disjoint sources -
there are usually some newer databases, older databases, some data feeds etc. A single request to
read or modify financial data can include calls to multiple services (whether such request is made
from a UI or web client or from some middleware). Some of such service calls might need to be
performed in parallel in order to get all the required replies earlier, some of the service calls
might be triggered only after the results of other service calls have arrived, since their input
might depend on them. Task
would be an ideal way of representing such service calls
since we can easily run multiple Task
in parallel and wait on a number
of Task
objects before starting other Task
s.
Services that provide Web Service interface can be
added to and accessed from .NET application as Service References
(the services themselves can be coded in Java or
C# or anything else - it does not matter as long as they are legal Web Services).
In .NET 4.5 the Service References can be configured to be accessed asynchronously as Tasks
.
Solution CallingServiceViaTasksTest consists of two projects:
- MockService - contains a very simple WCF service mocking a number of service calls returning
financial data.
- CallingServiceViaTasksTest - contains the test client that gets mock financial data and
prints it to the console
The service contains three methods (operations) available remotely:
- GetSecurityName - given a security id, it will return a security name -
in fact my mock implementation will return the same name
"MSFT" for anything you pass to it.
-
GetExchangesSecurityTradedOn - given security id, it will return the names of the exchanges
where this security is traded
-
GetSecurityExchangeData - given the security id and the exchange name it returns the price of the
security on that exchange.
Here is the very simple implementation of the mock service:
[ServiceContract]
public class MockFinancialDataService
{
[OperationContract]
public string GetSecurityName(string securityID)
{
return "MSFT";
}
[OperationContract]
public string[] GetExchangesSecurityTradedOn(string securityID)
{
return new string[] { "NASDAQ", "NYCE" };
}
[OperationContract]
public double GetSecurityExchangeData(string securityID, string exchangeName)
{
if (exchangeName == "NASDAQ")
return 20.00;
else if (exchangeName == "NYCE")
return 30.00;
return -1.0;
}
}
Assuming that you are building this project from scratch, you can add a service reference to the client
project by right-mouse clicking the References under the client project name within solution explorer and
choosing "Add Service Reference". Set "http://localhost:20575/MockFinancialDataService.svc" to be the
service URL, then click the button "Advanced" and make sure that "Allow generation of asynchronous operations"
checkbox is clicked and the option "Generate task-based operations" is chosen under this checkbox:
If you cannot select "Generate task-based operations" - it means that your client project is not
using .NET 4.5 (most likely it uses .NET 4.0) and you need to change your framework and then
reconfigure the service reference.
This client code is also very neat and simple (due to the TPL and async-await functionality):
public static async Task RunIt()
{
MockFinancialDataServiceClient client = new MockFinancialDataServiceClient();
Task<string> securityNameTask = client.GetSecurityNameAsync("1234");
Task<string[]> exchangesTask = client.GetExchangesSecurityTradedOnAsync("1234");
await Task.WhenAll(securityNameTask, exchangesTask);
string securityName = securityNameTask.Result;
foreach (string exchangeName in exchangesTask.Result)
{
double price = await client.GetSecurityExchangeDataAsync("1234", exchangeName);
Console.WriteLine(securityName + "\t" + exchangeName + "\t" + price);
}
}
We request the security name and the exchanges on which it trades in parallel. Then wait
until both produce results. After that, for each of the returned exchanges,
we request the price information and print out the security name, the exchange name and the price
to the console.
Now, try to imagine implementing the code above without TPL functionality. Instead of one little
neat function, you would have at least two functions (or lambdas) per remote call. On top of that,
you would have to add a lot of code for implementing the functionality to wait for the two first
asynchronous calls to complete before calling the GetSecurityExchangeDataAsync
remote method.
Remote Service Calls using Older API
If you company has not moved to .NET 4.5 yet, you can still use write the Task
wrappers around the asynchronous service calls. Solution CallingServicesViaTasksOlderAPITest
shows how to do it. It consists of the WCF MockService project - the same as in the previous
sample and the client project.
When the service reference is configured to generate async service calls, .NET 4.0 and earlier .NET
versions will generate methods to call the service in two different
ways - Event-based Asynchronous Pattern (EAP) and
Asynchronous Programming Model (APM). We took a look at EAP above - it creates a method to start
an asynchronous operation and a callback registered to fire when the async operation completes.
APM pattern has two methods - one to start an async operation, the other to block until the
operation completes. In case of our remote services - the APM methods to start an operation start
with "Begin" prefix, while those that block till the operation completes start with "End" prefix, e.g.
service operation GetSecurityName
results in two APM methods:
BeginGetSecurityName
to start the operation and EndGetSecurityName
to block till
operation completes.
We use the APM pattern to create Task
wrappers for calling async services - it can be
achieved even easier for APM pattern than for EAP one by using Task.Factory.FromAsync<TResult>(...)
function. Here is our client code:
static void Main(string[] args)
{
MockFinancialDataServiceClient client = new MockFinancialDataServiceClient();
Task<string> getNameTask = Task<string>.Factory.FromAsync<string>
(
client.BeginGetSecurityName,
client.EndGetSecurityName,
"1234", null );
Task<string[]> getExhcangesTask = Task<string[]>.Factory.FromAsync<string>
(
client.BeginGetExchangesSecurityTradedOn,
client.EndGetExchangesSecurityTradedOn,
"1234",
null
);
Task.WaitAll(getNameTask, getExhcangesTask);
string securityName = getNameTask.Result;
string[] exchanges = getExhcangesTask.Result;
foreach (string exchangeName in exchanges)
{
Task<double> priceTask = Task<double>.Factory.FromAsync<string, string>
(
client.BeginGetSecurityExchangeData,
client.EndGetSecurityExchangeData,
"1234",
exchangeName,
null
);
Console.WriteLine(securityName + "\t" + exchangeName + "\t" + priceTask.Result);
}
}
Task.Factory.FromAsync<TResult>(...)
method creates a task around two APM methods.
After these arguments, you need to pass the input arguments to the Begin...
APM method.
The last argument to FromAsync
is state
variable, which we do not use
and pass as null. FromAsync
has a bunch of overrides allowing to enter up to three
arguments to the Begin
APM function. If, however, the Begin
function
has more than three arguments, you can always wrap it within a lambda expression or a delegate
that takes less than three arguments and pass this lambda expression to FromAsync
.
Note, that our previous example of a service call (using .NET 4.5 functionality) will not block
the thread it runs on, e.g. the UI thread in WPF application. The current example of
using older .NET functionality, however, does block the current thread by its
Wait
or WaitAll
methods. So, if you do not want the thread to be blocked
by it, you can always start it on a different thread, e.g. by making it continue a Task
running on its own thread.
Wrapping a BackgroundWorker in a Task
Having Task
functionality made BackgroundWorker
functionality
largerly obsolete - you can achieve whatever you need using a Task
instead of
a BackgroundWorker
. Still there might be some reason you want to use
BackgroundWorker
functionality on the team - whether because your legacy
code uses it or because most of your team members or your boss love it and understand it better
than the newer Task
functionality.
Here, we show, how to wrap the BackgroundWorker
within Task
so that
you could gain all the flexibility of Task
functionality for
BackgroundWorker
based code.
Solution BackgroundWorkerTaskWrap shows how to wrap a background worker within a
Task
.
The task wrapping code is located under BackgroundWorkerUtils.ToTask()
extension function. It is TaskCompletionSource
based, so the resulting
task is started during the conversion.
It provides interface for Task
cancellation and progress reporting:
public static Task<object> ToTask
(
this BackgroundWorker backgroundWorker,
CancellationTokenSource cancellationTokenSource = null,
IProgress<object> progress = null
)
{
TaskCompletionSource<object> taskCompletionSource =
new TaskCompletionSource<object>(TaskCreationOptions.AttachedToParent);
if (cancellationTokenSource != null)
{
cancellationTokenSource.Token.Register
(
() =>
{
if (backgroundWorker.WorkerSupportsCancellation)
backgroundWorker.CancelAsync();
}
);
}
if (progress != null)
{
backgroundWorker.ProgressChanged += (sender, progressChangedArgs) =>
{
progress.Report(progressChangedArgs.ProgressPercentage);
};
}
RunWorkerCompletedEventHandler onCompleted = null;
onCompleted = (object sender, RunWorkerCompletedEventArgs e) =>
{
backgroundWorker.RunWorkerCompleted -= onCompleted;
if (e.Cancelled)
{
taskCompletionSource.SetCanceled();
taskCompletionSource.SetException(new OperationCanceledException());
}
else if (e.Error != null)
{
taskCompletionSource.SetException(e.Error);
}
else
{
taskCompletionSource.SetResult(e.Result);
}
};
backgroundWorker.RunWorkerCompleted += onCompleted;
backgroundWorker.RunWorkerAsync();
return taskCompletionSource.Task;
}
And here is an example of how to use the functionality from Program.cs file:
static void Main(string[] args)
{
BackgroundWorker backgroundWorker1 = new BackgroundWorker();
backgroundWorker1.DoWork += (sender, e) =>
{
Thread.Sleep(2000);
Console.WriteLine("this is backgroundWorker1");
};
BackgroundWorker backgroundWorker2 = new BackgroundWorker();
backgroundWorker2.DoWork += (sender, e) =>
{
Thread.Sleep(1000);
Console.WriteLine("this is backgroundWorker2");
};
BackgroundWorker backgroundWorker3 = new BackgroundWorker();
backgroundWorker3.DoWork += (sender, e) =>
{
Thread.Sleep(1000);
Console.WriteLine("this is backgroundWorker3");
};
Task<object> t1 = backgroundWorker1.ToTask();
Task<object> t2 = backgroundWorker2.ToTask();
Task.WhenAll(t1, t2).Wait();
Task<object> t3 = backgroundWorker3.ToTask();
t3.Wait();
}
}
In the sample above we schedule two background workers to run in parallel and the third one
to start when two first background workers completed their run.
Wrapping WPF Storyboards in Tasks
I always wanted be able to combine multiple storyboards, so that some of them would run in
parallel and then when they finish, some other animations would start. Task
functionality is ideal for making this happen.
The storyboard sample is located under WrappingStoryboardsInTasks project.
The XAML file of the project contains two rectangles blue and three Storyboard
objects.
First two storyboard rotate correspondingly blue and red rectangle, while the 3rd one increases their width.
On pressing "Start Animation" button we want to start the first two Storyboard
s and
when they both finish, we want to start the 3rd one. On top of that, once the StartAnimationButton
button is clicked,
we want to disable it and re-enable it once the last animation stops in order to prevent starting
the new storyboard run in the middle of the previous one.
Here is how simple the code becomes when we use the TPL and async-await functionality:
async void StartAnimationButton_Click(object sender, RoutedEventArgs e)
{
StartAnimationButton.IsEnabled = false;
Storyboard storyboard1 = (Storyboard)MyGrid.Resources["Storyboard1"];
Storyboard storyboard2 = (Storyboard)MyGrid.Resources["Storyboard2"];
Storyboard storyboard3 = (Storyboard)MyGrid.Resources["Storyboard3"];
Task<object> t1 = storyboard1.ToTask();
Task<object> t2 = storyboard2.ToTask();
await Task.WhenAll(t1, t2);
await storyboard3.ToTask();
StartAnimationButton.IsEnabled = true;
}
The code results in a number of callbacks - one to start storboard3
,
another to enable the StartAnimationButton
, but it all looks like
a linear synchronous code due to the TPL and async-await magic.
The code for converting Storyboard
s to Task
s is placed into
StoryboardToTask
static class as ToTask(...)
extension method:
public static Task<object> ToTask
(
this Storyboard storyboard,
CancellationTokenSource cancellationTokenSource = null
)
{
TaskCompletionSource<object> taskCompletionSource =
new TaskCompletionSource<object>(TaskCreationOptions.AttachedToParent);
if (cancellationTokenSource != null)
{
cancellationTokenSource.Token.Register
(
() =>
{
storyboard.Stop();
}
);
}
EventHandler onCompleted = null;
onCompleted = (object sender, EventArgs e) =>
{
storyboard.Completed -= onCompleted;
taskCompletionSource.SetResult(null);
};
storyboard.Completed += onCompleted;
storyboard.Begin();
return taskCompletionSource.Task;
}
Note that we use TaskCompletionSource
to create a Task
wrapper
around the Storyboards
, so we should be starting
the Storyboard
during the conversion.
Storyboard
s themselves consist of animation objects. Eventually (time permitting)
I would like to use
Task
objects to wrap individual animations and built a new type of Storyboard
-like
objects having different animation starting based on when the previous animations finished instead of
being time based. This would result in the animation objects that are easier to build and control.
Summary
In this article we provided information on using TPL and async-await features for creating asynchronous
functionality that is almost as simple as the synchronous code.
We showed how to apply these concepts to sending and processing numerous service requests, scheduling
multiple BackgroundWorker
s and Storyboard
s.