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的并行运算施用就是对用户自定义函数的并行运算.原理上就是把一个输入流截分成多个输入流并行地输入到一个自定义函数的多个运行实例.这些函数运行实例同时在各自不同的线程里同步运算直至耗尽所有输入. ...
随机推荐
- PHP 类和继承
//定义一个超类 //public 和 protectd属性和方法可以继承,private不可继承. class A{ public $a =0; private $b = 1; protected ...
- php之面向对象(1)
讲到面向对象 先回顾下以前的编程思路,所谓编程思路就是根据知识本质原理通过逻辑推理程序的过程,编程思路,讲究的是先明确要做的事情是怎么.离开代码的情况下,自己也要能明白这一件事情怎么做.而不是把代码背 ...
- 整理grep实战文本搜索过滤技巧
一:grep的简介: 文本搜索工具,根据用户指定的文本模式对目标文件进行逐行搜索,显示能够被模式所匹配到的行.配合正则表达式的使用可以实现强大的文本处理.下面一一说明正则的例子. 二:文本处理工具分类 ...
- 'InitializeCulture' is not a member of 'XXXX'
今天在部署ASP.NET网页时出现一个奇怪的问题.在开发,调试时访问网页一切正常.但一部署到IIS中时就会出现以下的错误信息:'InitializeCulture' is not a member o ...
- 将 Photoshop CC 2015.5 英文界面换成中文, 英文与中文界面互换
注:转载或引用请注明出处 在英文的win server 2012 r2 上安装PS CC 2015.5 时,安装程序自动按成了英文版的PS,那么如何将英文换成中文呢? 网上大多讲的都是将中文换成英文, ...
- Python/Numpy大数据编程经验
Python/Numpy大数据编程经验 1.边处理边保存数据,不要处理完了一次性保存.不然程序跑了几小时甚至几天后挂了,就啥也没有了.即使部分结果不能实用,也可以分析程序流程的问题或者数据的特点. ...
- unity 引用 移动mm 支付sdk
原地址:http://blog.csdn.net/u012085988/article/details/17531005 资源下载:http://download.csdn.net/detail/u0 ...
- 静态分析安全测试(SAST)优缺点探析
静态分析安全测试(SAST)是指不运行被测程序本身,仅通过分析或者检查源程序的语法.结构.过程.接口等来检查程序的正确性,那么采用静分析安全测试的方法有什么优缺点呢,且让小编给你说道说道. 许多公司都 ...
- 部分视图调用方法总结(Action 、 RenderAction 、 Partial 、 RenderPartial)
Action . RenderAction . Partial . RenderPartial 1.Action . RenderAction Action 方法以字符串的形式返回子视图,可以直接被呈 ...
- 《php和mysql web开发》读书笔记
总算是强迫自己把第一篇给看完了,在这里做一个小结,将一些知识点记录下来. 一.第一篇 使用PHP 1.php中的注释.php支持c.c++和shell脚本风格注释 /**/多行注释 //单行注释 ...