TPL - Task Parallel Library为我们提供了Task相关的api,供我们非常方便的编写并行代码,而不用自己操作底层的Thread类。使用Task的优势是显而易见的:

  • 提供返回值

  • 异常捕获

  • 节省Context Switch造成的开销

另一个Task带来的优势就是不再需要通过阻塞线程来等待Task结束,如果需要在Task结束时开启另一项任务,可以使用Task.ContinueWith这个方法,并传入一个指定的委托即可。而本文主要关注ContinueWith中的TaskContinuationsOptions参数中的ExecuteSynchronously这个枚举值

ExecuteSynchronously是什么

我们先来看一下官方文档对于ExecuteSynchronously给出的解释

Specifies that the continuation task should be executed synchronously. With this option specified, the continuation runs on the same thread that causes the antecedent task to transition into its final state. If the antecedent is already complete when the continuation is created, the continuation will run on the thread that creates the continuation. If the antecedent's CancellationTokenSource is disposed in a finally block (Finally in Visual Basic), a continuation with this option will run in that finally block. Only very short-running continuations should be executed synchronously.

一大长串,我们尝试解析一下这一堆话在说什么。首先,当调用者传入这个枚举值后,意味着ContinueWith中传入的委托将会在原Task的同一线程上执行,但要注意的是,这里的同一线程指的是:将原Task转移到final state的线程。因为原Task的执行可能涉及了多个线程,因此这里特意指明是final state对应的线程,而不是从所有涉及的线程中随机挑选一个。

其次,如果调用ContinueWith的时候,原Task已经执行完毕,那么continue的委托并不会在刚才提到的那个final state对应的线程上执行,而是由创建这个continuation的线程执行。

最后一点,如果原Task的CancellationTokenSource在finally块中调用了Dispose方法,那么continue的委托就会在那个finally块中执行。(其实这一点我也没有理解到底是什么意思,欢迎大神拍砖)

举个例子

     class Program
{
static void Main(string[] args)
{
for (int i = ; i < ; i++)
{
Task.Run(async () =>
{
Console.WriteLine($"Running on thread {Thread.CurrentThread.ManagedThreadId}");
await Task.Delay();
});
}
Task t = Task.Run(async () =>
{
Console.WriteLine($"=======Running on thread {Thread.CurrentThread.ManagedThreadId}");
await Task.Delay();
Console.WriteLine($"=======Running on thread {Thread.CurrentThread.ManagedThreadId}");
}); // Thread.Sleep(5000);
t.ContinueWith(_ =>
{
Console.WriteLine($"*******Running on thread {Thread.CurrentThread.ManagedThreadId}");
}, TaskContinuationOptions.ExecuteSynchronously); Console.ReadLine();
}
}

这段代码首先创建了30个干扰Task,这样能显著降低即使不用ExecuteSynchronously,线程池也会分配原线程来执行Continue任务的概率。运行后发现,任务t和continue确实是在同一个线程上执行的。而注释掉TaskContinuationOptions.ExecuteSynchronously后,continue就会由线程池重新分配线程。而如果取消注释线程Sleep 5秒这行代码,即使ExecuteSynchronously,continue也会由线程池重新分配线程执行,这正如上一段文档中提到的:调用ContinueWith时,如果原任务已经执行完毕,那么会由调用ContinueWith的线程执行continue任务,在这里就会由主线程来执行continue任务。

ExecuteSynchronously为什么不是默认行为

微软工程师Stephen Toub在其一篇博文中解释了为什么.NET团队没有把ExecuteSynchronously作为默认方案。

  1. 一个Task任务有可能会多次调用ContinueWith方法,如果默认是在同一线程执行,那么所有的continue任务都需要等待上一个continue完成后才能执行,这也就失去了并行的意义。

  2. 还有一种常见的情况就是很多个continue任务一个接一个的串在一起,如果这些continue任务都是同步顺序执行的,一个任务完成了就会执行下一个任务,这将导致线程栈上堆积的frame越来越多,这有可能会导致线程栈溢出。

  3. 为了解决溢出的问题,通常的解决方式是借用一个“蹦床”,把需要完成的工作在当前线程栈之外保存起来,然后利用一个更高level的frame检索存储的任务并执行。这样一来,每次完成一个任务之后,并不是立即执行下一个任务,而是将其保存至上述的frame并退出,该frame将执行下一个任务。而TPL正是利用这一方式来提升异步的执行效率。

以上就是没有默认同步运行任务的主要原因,虽然性能上会稍有损失,但这样可以更好的利用并行,更安全,而这性能的损失通常来说并不是最重要的。作者最后也建议我们如果Task里的语句很简单的话,同步执行也是值得的。正如官方文档最后一句提到的:

Only very short-running continuations should be executed synchronously.

如果是一个复杂又耗时的任务以同步方式来执行的话就有点得不偿失了。

ExecuteSynchronously在什么情况下不会同步执行

Stephen Toub提到,即使在调用ContinueWith的时候传入了TaskContinuationOptions.ExecuteSynchronously,CLR也只能尽量让continue在原Task线程上执行,但无法100%保证。

  1. 如果原Task的线程被Abort,那么与其关联的continue任务是无法在原线程上执行的。

  2. 在上一段中我们也提到了关于线程栈溢出的问题,如果TPL认为接着在该线程上运行continue任务有溢出的风险,continue任务就会转而变成异步执行。

  3. 最后一种情况就是Task Scheduler不允许同步执行Task,开发者可以自定义一个TaskScheduler,重写父类方法,决定任务的执行方式。

最后欢迎关注我的个人公众号:SoBrian,期待与大家共同交流,共同成长!

Reference

TaskContinuationsOptions.ExecuteSynchronously探秘的更多相关文章

  1. C#服务器获取客户端IP地址以及归属地探秘

    背景:博主本是一位Windows桌面应用程序开发工程师,对网络通信一知半解.一日老婆逛完某宝,问:"为什么他们知道我的地址呢,他们是怎么获取我的地址的呢?" 顺着这个问题我们的探秘 ...

  2. Composer概述及其自动加载探秘

    composer概述 一开始,最吸引我的当属 Composer 了,因为之前从没用过 Composer . Composer 是PHP中用来管理依赖关系的工具,你只需在自己的项目中声明所依赖的外部工具 ...

  3. 基于AngularJS的个推前端云组件探秘

    基于AngularJS的个推前端云组件探秘 AngularJS是google设计和开发的一套前端开发框架,帮助开发人员简化前端开发的负担.AngularJS将帮助标准化的开发web应用结构并且提供了针 ...

  4. .NET跨平台之旅:探秘 dotnet run 如何运行 .NET Core 应用程序

    自从用 dotnet run 成功运行第一个 "Hello world" .NET Core 应用程序后,一直有个好奇心:dotnet run 究竟是如何运行一个 .NET Cor ...

  5. ASP.Net WebForm温故知新学习笔记:二、ViewState与UpdatePanel探秘

    开篇:经历了上一篇<aspx与服务器控件探秘>后,我们了解了aspx和服务器控件背后的故事.这篇我们开始走进WebForm状态保持的一大法宝—ViewState,对其刨根究底一下.然后,再 ...

  6. ASP.Net WebForm温故知新学习笔记:一、aspx与服务器控件探秘

    开篇:毫无疑问,ASP.Net WebForm是微软推出的一个跨时代的Web开发模式,它将WinForm开发模式的快捷便利的优点移植到了Web开发上,我们只要学会三步:拖控件→设属性→绑事件,便可以行 ...

  7. 探秘Tomcat——连接器和容器的优雅启动

    前言: 上篇<探秘Tomcat——启动篇>粗线条的介绍了在tomcat在启动过程中如何初始化Bootstrap类,加载并执行server,从而启动整个tomcat服务,一直到我们看到控制台 ...

  8. 探秘Tomcat——启动篇

    tomcat作为一款web服务器本身很复杂,代码量也很大,但是模块化很强,最核心的模块还是连接器Connector和容器Container.具体请看下图: 从图中可以看出 a. 高亮的两块是Conne ...

  9. 探秘Tomcat——从一个简陋的Web服务器开始

    前言: 无论是之前所在实习单位小到一个三五个人做的项目,还是如今一个在做的百人以上的产品,一直都能看到tomcat的身影.工作中经常遇到的操作就是启动和关闭tomcat服务,或者修改了摸个java文件 ...

随机推荐

  1. 2020-06-21:ZK的哪些基础能力构建出了哪些上层应用?

    福哥答案2020-06-21: 福哥口诀法:数负命Ma集配分(使用场景:数据发布订阅.负载均衡.命名服务.Master 选举.集群管理.配置管理.分布式队列和分布式锁) 数据发布订阅:dubbo的rp ...

  2. 2020-06-14:Redis怎么实现分布式锁?

    福哥答案2020-06-14: 1.SETNX+EXPIRE.非原子性.2.SET key value [EX seconds] [PX milliseconds] [NX|XX]EX second ...

  3. 解Bug之路-dubbo流量上线时的非平滑问题

    前言 笔者最近解决了一个困扰了业务系统很久的问题.这个问题只在发布时出现,每次只影响一两次调用,相较于其它的问题来说,这个问题有点不够受重视.由于种种原因,使得这个问题到了业务必须解决的程度,于是就到 ...

  4. Vercel托管博客

    背景 本站已托管至Vercel,但作为一名前端小白,昨天帮朋友托管博客到Vercel的过程中到处碰壁,就想写一篇博客记录一下. 注册登录 点击Vercel官网{% btn 'https://verce ...

  5. wordpress-技术博客主题推荐

    推荐主题 1.WordStar 这个主题是干净的,以博客为中心,设计清晰,简单,直接的排版,可在各种各样的屏幕尺寸可读,适合多种语言. 效果图 还是非常简洁, 基本和CSDN差不多了 除了没有广告以外 ...

  6. day1 计算机的组成

    1.计算机,是用于高速计算的电子机器,具有数值计算, 逻辑判断,储存记忆的功能. 2.计算机由硬件系统和软件系统组成构成. 3.计算机中所有的运行程序都是在内存中进行的,暂时存放在CPU中的运算数据 ...

  7. python列表的增删改查和嵌套

    列表 python常用的数据类型 可承载任意的数据类型 列表是有序的,可索引.切片(步长) 列表的创建 list1 = [1, 2, 'whll'] #1. list2 = list() #2. #3 ...

  8. 理解C#中的ConfigureAwait

    原文:https://devblogs.microsoft.com/dotnet/configureawait-faq/ 作者:Stephen 翻译:xiaoxiaotank 静下心来,你一定会有收获 ...

  9. jsp环境搭建及入门

    配置环境: 此处配置完成后startup.bat闪退,修改端口号重启后恢复正常 常见状态码: 200:一切正常 300/301:页面重定向(跳转) 404:资源不存在 403:权限不足(例如:访问a目 ...

  10. Spring Security-获取当前登录用户的详细信息

    在Spring框架里面,可以通过以下几种方式获取到当前登录用户的详细信息: 1. 在Bean中获取用户信息 Authentication authentication = SecurityContex ...