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的并行运算施用就是对用户自定义函数的并行运算.原理上就是把一个输入流截分成多个输入流并行地输入到一个自定义函数的多个运行实例.这些函数运行实例同时在各自不同的线程里同步运算直至耗尽所有输入. ...
随机推荐
- POJ1384完全背包问题
题目大意:给你一个储蓄罐空的,和满的重量,然后给出各种硬币的价值和对应的重量,要你估计出储蓄罐里面硬币价值和最小为多少,注意要保证重量和恰好为给出满的重量解题思路:完全背包问题,只是求最小值,注意初始 ...
- rar压缩文件下载
//string fileName = "ceshi.rar";//客户端保存的文件名 //string filePath = Server.MapPath(&qu ...
- tp数据表字段缓存
在维护一个tp写的项目,因为需要在产品表product中增加了一个字段status,但是不论如何就是无法给status赋值,查了资料才发现,原来是tp的数据表字段缓存在搞鬼. 在runtime> ...
- IdTCPServer, idTCPClient
IdTcpServer uses IdContext //需要引用 属性,方法: IdTCPServer.Active :=True; //开启服务器 IdTCPServer1.Bindings.Ad ...
- python--threading多线程总结
threading用于提供线程相关的操作,线程是应用程序中工作的最小单元.python当前版本的多线程库没有实现优先级.线程组,线程也不能被停止.暂停.恢复.中断. threading模块提供的类: ...
- GPS坐标转百度地图并且加载地图示例.支持微信端访问
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Wx_VehicleLoca ...
- 【配置文件节点】java世界配置文件节点
Spring <context:property-placeholder/> 期望:能不能有一种解决方案可以方便我们在一个阶段内不需要频繁书写一个参数的值,而在不同阶段间又可以方便的切换参 ...
- Servlet的getContextPath(), getServletPath(), getRequestURI(), getRealPath("/")
假定web application 名称为news,你在浏览器中输入请求路径: http://localhost:8080/news/main/list.jsp 则执行下面向行代码后打印出如下结果: ...
- JS & DOM 对象
22:36 2013/6/4 详情参照W3C文档标准 Browser 对象(顶层对象) DOM Window DOM Navigator DOM Screen DOM History DOM Loca ...
- 微软Hololens学院教程-Hologram 211-Gestures(手势)【微软教程已经更新,本文是老版本】
这是老版本的教程,为了不耽误大家的时间,请直接看原文,本文仅供参考哦!原文链接:https://developer.microsoft.com/EN-US/WINDOWS/HOLOGRAPHIC/ho ...