Using the Task Parallel Library (TPL) for Events
Using the Task Parallel Library (TPL) for Events
The parallel tasks library was introduced with the .NET Framework 4.0 and is designed to simplify parallelism and concurrency. The API is very straightforward and usually involves passing in an Action to execute. Things get a little more interesting when you are dealing with asynchronous models such as events.
While the TPL has explicit wrappers for the asynchronous programming model (APM) that you can read about here: TPL APM Wrappers, there is no explicit way to manage events.
I usually hide the "muck" of subscribing and waiting for a
completed action in events with a callback. For example, the following
method generates a random number. I'm using a delay to simulate a
service call and a thread task to make the call back asynchronous: you
call into the method, then provide a delegate that is called once the
information is available.
private static void _GenerateRandomNumber(Action<int> callback)
{
var random = _random.Next(0, 2000) + 10;
Console.WriteLine("Generated {0}", random);
Task.Factory.StartNew(() =>
{
Thread.Sleep(1000);
callback(random);
}, TaskCreationOptions.None);
}
Now consider an algorithm that requires three separate calls to complete to provide the input values in order to compute a result. The calls are independent so they can be done in parallel. The TPL supports "parent" tasks that wait for their children to complete, and a first pass might look like this:
private static void _Incorrect()
{ var start = DateTime.Now; int x = 0, y = 0, z = 0; Task.Factory.StartNew(
() =>
{
Task.Factory.StartNew(() => _GenerateRandomNumber(result => x = result),
TaskCreationOptions.AttachedToParent);
Task.Factory.StartNew(() => _GenerateRandomNumber(result => y = result),
TaskCreationOptions.AttachedToParent);
Task.Factory.StartNew(() => _GenerateRandomNumber(result => z = result),
TaskCreationOptions.AttachedToParent);
}).ContinueWith(t =>
{
var finish = DateTime.Now;
Console.WriteLine("Bad Parallel: {0}+{1}+{2}={3} [{4}]",
x, y, z,
x+y+z,
finish - start);
_Parallel();
});
}
The code aggregates several tasks to the parent, the parent then waits for the children to finish and continues by computing the time span and showing the result. While the code executes extremely fast, the result is not what you want. Take a look:
Press ENTER to begin (and again to end) Generated 593
Generated 1931
Generated 362
Bad Parallel: 0+0+0=0 [00:00:00.0190011]
You can see that three numbers were generated, but nothing was computed in the sum. The reason is that for the purposes of the TPL, the task ends when the code called ends. The TPL has no way to know that the callback was handed off to an asynchronous process (or event) and therefore considers the task complete once the generate call finishes executing. This returns and falls through and the computation is made before the callback fires and updates the values.
So how do you manage this and allow the tasks to execute in parallel but still make sure the values are retrieved?
For this purpose, the TPL provides a special class called TaskCompletionSource<T>.
The task completion source is a point of synchronization that you can
use to complete an asynchronous or event-based task and relay the
result. The underlying task won't complete until an exception is thrown
or the result is set.
To see how this is used, let's take the existing method and fix it using the completion sources:
private static void _Parallel()
{
var taskCompletions = new[]
{
new TaskCompletionSource<int>(),
new TaskCompletionSource<int>(),
new TaskCompletionSource<int>()
}; var tasks = new[] {taskCompletions[0].Task, taskCompletions[1].Task, taskCompletions[2].Task}; var start = DateTime.Now; Task.Factory.StartNew(() => _GenerateRandomNumber(result => taskCompletions[0].TrySetResult(result)));
Task.Factory.StartNew(() => _GenerateRandomNumber(result => taskCompletions[1].TrySetResult(result)));
Task.Factory.StartNew(() => _GenerateRandomNumber(result => taskCompletions[2].TrySetResult(result))); Task.WaitAll(tasks); var finish = DateTime.Now;
Console.WriteLine("Parallel: {0}+{1}+{2}={3} [{4}]",
taskCompletions[0].Task.Result,
taskCompletions[1].Task.Result,
taskCompletions[2].Task.Result,
taskCompletions[0].Task.Result + taskCompletions[1].Task.Result + taskCompletions[2].Task.Result,
finish - start);
}
First, I create an array of the task completions. This makes for an easy reference to coordinate the results. Next, I create an array of the underlying tasks. This provides a collection to pass to Task.WaitAll() to synchronize all return values before computing the result. Instead of using variables, the tasks now use the TaskCompletionSource to set the results after the simulated callback. The tasks won't complete until the result is set, so all values are returned before the final computation is made. Here are the results:
Generated 279
Generated 618
Generated 1013
Parallel: 618+279+1013=1910 [00:00:01.9981143]
You can see that all generated numbers are accounted for and properly added. You can also see that the tasks ran in parallel because it completed in under 2 seconds when each call had a 1 second delay.
The entire console application can simply be cut and pasted from
the following code — there are other ways to chain the tasks and make
the completions fall under a parent but this should help you get your
arms wrapped around dealing with tasks that don't complete when the
methods return, but require a synchronized completion context.
class Program
{
private static readonly Random _random = new Random(); static void Main(string[] args)
{
Console.WriteLine("Press ENTER to begin (and again to end)");
Console.ReadLine(); _Incorrect(); Console.ReadLine();
} private static void _Incorrect()
{ var start = DateTime.Now; int x = 0, y = 0, z = 0; Task.Factory.StartNew(
() =>
{
Task.Factory.StartNew(() => _GenerateRandomNumber(result => x = result),
TaskCreationOptions.AttachedToParent);
Task.Factory.StartNew(() => _GenerateRandomNumber(result => y = result),
TaskCreationOptions.AttachedToParent);
Task.Factory.StartNew(() => _GenerateRandomNumber(result => z = result),
TaskCreationOptions.AttachedToParent);
}).ContinueWith(t =>
{
var finish = DateTime.Now;
Console.WriteLine("Bad Parallel: {0}+{1}+{2}={3} [{4}]",
x, y, z,
x+y+z,
finish - start);
_Parallel();
});
} private static void _Parallel()
{
var taskCompletions = new[]
{
new TaskCompletionSource<int>(),
new TaskCompletionSource<int>(),
new TaskCompletionSource<int>()
}; var tasks = new[] {taskCompletions[0].Task, taskCompletions[1].Task, taskCompletions[2].Task}; var start = DateTime.Now; Task.Factory.StartNew(() => _GenerateRandomNumber(result => taskCompletions[0].TrySetResult(result)));
Task.Factory.StartNew(() => _GenerateRandomNumber(result => taskCompletions[1].TrySetResult(result)));
Task.Factory.StartNew(() => _GenerateRandomNumber(result => taskCompletions[2].TrySetResult(result))); Task.WaitAll(tasks); var finish = DateTime.Now;
Console.WriteLine("Parallel: {0}+{1}+{2}={3} [{4}]",
taskCompletions[0].Task.Result,
taskCompletions[1].Task.Result,
taskCompletions[2].Task.Result,
taskCompletions[0].Task.Result + taskCompletions[1].Task.Result + taskCompletions[2].Task.Result,
finish - start);
} private static void _GenerateRandomNumber(Action<int> callback)
{
var random = _random.Next(0, 2000) + 10;
Console.WriteLine("Generated {0}", random);
Task.Factory.StartNew(() =>
{
Thread.Sleep(1000);
callback(random);
}, TaskCreationOptions.None);
}
}
Using the Task Parallel Library (TPL) for Events的更多相关文章
- TPL(Task Parallel Library)多线程、并发功能
The Task Parallel Library (TPL) is a set of public types and APIs in the System.Threading and System ...
- Winform Global exception and task parallel library exception;
static class Program { /// <summary> /// 应用程序的主入口点. /// </summary> [STAThread] static vo ...
- Task Parallel Library01,基本用法
我们知道,每个应用程序就是一个进程,一个进程有多个线程.Task Parallel Library为我们的异步编程.多线程编程提供了强有力的支持,它允许一个主线程运行的同时,另外的一些线程或Task也 ...
- C#5.0之后推荐使用TPL(Task Parallel Libray 任务并行库) 和PLINQ(Parallel LINQ, 并行Linq). 其次是TAP(Task-based Asynchronous Pattern, 基于任务的异步模式)
学习书籍: <C#本质论> 1--C#5.0之后推荐使用TPL(Task Parallel Libray 任务并行库) 和PLINQ(Parallel LINQ, 并行Linq). 其次是 ...
- C#~异步编程再续~大叔所理解的并行编程(Task&Parallel)
返回目录 并行这个概念出自.net4.5,它被封装在System.Threading.Tasks命名空间里,主要提供一些线程,异步的方法,或者说它是对之前Thread进行的二次封装,为的是让开发人员更 ...
- Task/Parallel实现异步多线程
代码: #region Task 异步多线程,Task是基于ThreadPool实现的 { //TestClass testClass = new TestClass(); //Action<o ...
- 异步和多线程,委托异步调用,Thread,ThreadPool,Task,Parallel,CancellationTokenSource
1 进程-线程-多线程,同步和异步2 异步使用和回调3 异步参数4 异步等待5 异步返回值 5 多线程的特点:不卡主线程.速度快.无序性7 thread:线程等待,回调,前台线程/后台线程, 8 th ...
- Task Parallel Library02,更进一步
在前一篇中,了解了Task的基本用法 如果一个方法返回Task,Task<T>,如何获取Task的返回值,获取值的过程会阻塞线程吗? static void Main(string[] a ...
- FunDA(15)- 示范:任务并行运算 - user task parallel execution
FunDA的并行运算施用就是对用户自定义函数的并行运算.原理上就是把一个输入流截分成多个输入流并行地输入到一个自定义函数的多个运行实例.这些函数运行实例同时在各自不同的线程里同步运算直至耗尽所有输入. ...
随机推荐
- 初识jQuery(适合初学者哟.........)
您要知道!! jQuery是目前使用最广泛的javascript函数库.据统计,全世界排名前100万的网站,有46%使用jQuery,远远超过其他库. 微软公司甚至把jQuery作为他们的官方库.对 ...
- 【分享】 高级Visual Basic 编程 清晰pdf+随书源代码光盘
搞vb6的可能不多,博客园也大多是.net java,近日在网上找到这本好书,想要成为vb高手,这本书不要错过,学完你会发现win32下,vb6还真是无所不能.可贵的是本书的作者是当时vb6 IDE的 ...
- poj 2001 Shortest Prefixes trie入门
Shortest Prefixes 题意:输入不超过1000个字符串,每个字符串为小写字母,长度不超过20:之后输出每个字符串可以简写的最短前缀串: Sample Input carbohydrate ...
- 一步步学习NHibernate(8)——HQL查询(2)
请注明转载地址:http://www.cnblogs.com/arhat 在上一章中,老魏带着大家学习了HQL语句,发现HQL语句还是非常不错的,尤其是在懒加载的时候,书写起来比较的舒服,但是这里老魏 ...
- Spring MVC常用的注解
@Controller @Controller 负责注册一个bean 到spring 上下文中,bean 的ID 默认为 类名称开头字母小写,你也可以自己指定,如下 方法一: @Controller ...
- iOS实现地图半翻页效果--老代码备用参考
// Curl the image up or down CATransition *animation = [CATransition animation]; [animation setDurat ...
- 转 scrollLeft,scrollWidth,clientWidth,offsetWidth之完全详解
scrollHeight: 获取对象的滚动高度. scrollLeft:设置或获取位于对象左边界和窗口中目前可见内容的最左端之间的距离 scrollTop:设置或获取位于对象最顶端和窗口中可见内容的最 ...
- 南方数据企业0day
漏洞影响版本 v10.0 v11.0 关键字:inurl:”HomeMarket.asp” 默认后台:/admin 直接爆用户密码: http://www.xxx.com/NewsType.asp?S ...
- yii
2008年出现的一个以php为基础的框架,特点是:高性能框架.代码重用性.速度非常快(改完代码后直接刷新就可以展示修改后的页面).有小物件.登录组件.日志组件等等. main.php配置与数据库相连的 ...
- C#博文搜集
1. abstract (抽象类) 参考1 2. interface (接口) 参考1 3. 委托 C#委托学习