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的更多相关文章

  1. TPL(Task Parallel Library)多线程、并发功能

    The Task Parallel Library (TPL) is a set of public types and APIs in the System.Threading and System ...

  2. Winform Global exception and task parallel library exception;

    static class Program { /// <summary> /// 应用程序的主入口点. /// </summary> [STAThread] static vo ...

  3. Task Parallel Library01,基本用法

    我们知道,每个应用程序就是一个进程,一个进程有多个线程.Task Parallel Library为我们的异步编程.多线程编程提供了强有力的支持,它允许一个主线程运行的同时,另外的一些线程或Task也 ...

  4. 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). 其次是 ...

  5. C#~异步编程再续~大叔所理解的并行编程(Task&Parallel)

    返回目录 并行这个概念出自.net4.5,它被封装在System.Threading.Tasks命名空间里,主要提供一些线程,异步的方法,或者说它是对之前Thread进行的二次封装,为的是让开发人员更 ...

  6. Task/Parallel实现异步多线程

    代码: #region Task 异步多线程,Task是基于ThreadPool实现的 { //TestClass testClass = new TestClass(); //Action<o ...

  7. 异步和多线程,委托异步调用,Thread,ThreadPool,Task,Parallel,CancellationTokenSource

    1 进程-线程-多线程,同步和异步2 异步使用和回调3 异步参数4 异步等待5 异步返回值 5 多线程的特点:不卡主线程.速度快.无序性7 thread:线程等待,回调,前台线程/后台线程, 8 th ...

  8. Task Parallel Library02,更进一步

    在前一篇中,了解了Task的基本用法 如果一个方法返回Task,Task<T>,如何获取Task的返回值,获取值的过程会阻塞线程吗? static void Main(string[] a ...

  9. FunDA(15)- 示范:任务并行运算 - user task parallel execution

    FunDA的并行运算施用就是对用户自定义函数的并行运算.原理上就是把一个输入流截分成多个输入流并行地输入到一个自定义函数的多个运行实例.这些函数运行实例同时在各自不同的线程里同步运算直至耗尽所有输入. ...

随机推荐

  1. 微软自带iscsi客户端对iqn的要求

    节点名称:Microsoft iSCSI 发起程序严格遵守为 iSCSI 节点名称指定的规则.这些规则也适用于 Microsoft iSCSI 发起程序节点名称以及发现的任何目标节点名称.构建 iSC ...

  2. Java官方Demo Mark

    Java2D里四个重要的基类:AnimatingSurface:              动画界面基类ControlsSurface:              控制界面基类AnimatingCon ...

  3. C# 链表操作

    关于链表操作,在C#当中微软已经提供了一个LinkedList<T>的数据结构,通过这个类提供的一系列方法就能够实现链表操作. 这里我提供一段代码,这是在论坛里面有人提问时给出的代码,它实 ...

  4. mouseover与mouseenter与mousemove的区别mouseout与mouseleave的区别

    <html> <head> <title></title> </head> <body> <p> 当鼠标进入div1 ...

  5. [C#]获取一年中是第几个星期

    关键代码: /// <summary> /// 获取日期是一年中第几个星期 /// </summary> /// <param name="date" ...

  6. “微信应用号对行业影响”之一,app开发速来围观

    昨天,微信张小龙的一个讲话刷爆朋友圈,除了4大价值观,最后顺便提到:要推出微信应用号! 其实,价值观也就说说听听,最后顺便提到的微信应用号,才是真正的巨型炸弹. 腾讯挟6亿高粘度用户之重,号令天下,阿 ...

  7. javascript检测是否安装了flash

    检测是否安装了flash function flashChecker() { var hasFlash = 0; //是否安装了flash var flashVersion = 0; //flash版 ...

  8. struts2,登录功能模块实现

    功能: ·UserLogin作为控制登录的Action,校验密码成功后记录session,可以选择记住登陆状态,登陆成功后自动跳转到登陆前的URL: ·UserLogout作为控制登录推出的Actio ...

  9. SQL中约束和触发器的停用与启用

    如何对SQL中的约束和触发器进行停用与启用,如果有外键约束则相关联表都要进行相应操作. ALTER TABLE TableName CHECK CONSTRAIT ALL --检查约束 ALTER T ...

  10. Sublime Text 2 插件

    一直以来写代码都是用的EditPlus,也尝试了一段时间学习Vim这神器,后来因为使用不习惯还是改回了原来的EditPlus.前几天朋友想我推荐了Sublime Text 2,喜欢尝鲜我的肯定是不会放 ...