.NET Task揭秘(一)

Task为.NET提供了基于任务的异步模式,它不是线程,它运行在线程池的线程上。本着开源的精神, 本文以解读基于.NET4.5 Task源码的方式来揭秘Task的实现原理。

 

Task的创建

Task的创建方式主要有2种:Task.Run 和Task.Factory.StartNew,各自有不同的overload,这里只解读其中的一种方式,其他有兴趣的请自行解读。

先来看看Task.Run源码:

public static Task Run(Action action, CancellationToken cancellationToken)
{
StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
return Task.InternalStartNew((Task) null, (Delegate) action, (object) null, cancellationToken, TaskScheduler.Default, TaskCreationOptions.DenyChildAttach, InternalTaskOptions.None, ref stackMark);
}

调用了Task.InternalStartNew,第一个参数为null,并传入TaskScheduler.DefaultTaskCreationOptions.DenyChildAttach.

再来看看Task.Factory.StartNew源码:

public Task StartNew(Action<object> action, object state, CancellationToken cancellationToken)

{

StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;

Task internalCurrent = Task.InternalCurrent;

return Task.InternalStartNew(internalCurrent, (Delegate) action, state, cancellationToken, this.GetDefaultScheduler(internalCurrent), this.m_defaultCreationOptions, InternalTaskOptions.None, ref stackMark);

}

也是调用Task.InternalStartNew,第一个参数为internalCurrent,当前为null,并传入GetDefaultScheduler(internalCurrent)m_defaultCreationOptions


private TaskScheduler GetDefaultScheduler(Task currTask)

{

if (this.m_defaultScheduler != null)

return this.m_defaultScheduler;

if (currTask != null && (currTask.CreationOptions & TaskCreationOptions.HideScheduler) == TaskCreationOptions.None)

return currTask.ExecutingTaskScheduler;

return TaskScheduler.Default;

}

如果internalCurrent不为空而且options是TaskCreationOptions.HideScheduler,那么启用internalCurrent的TaskScheduler。可惜internalCurrent为null,所以启用默认的TaskScheduler,跟入代码发现默认的TaskScheduler是ThreadPoolTaskScheduler,看名字就知道用的是线程池的任务调度,跟“黑盒”传说的一样的。m_defaultCreationOptions在Task.Factory的默认无参构造函数里被赋值TaskCreationOptions.None

public abstract class TaskScheduler

{

private static readonly ConditionalWeakTable<TaskScheduler, object> s_activeTaskSchedulers = new ConditionalWeakTable<TaskScheduler, object>();

private static readonly TaskScheduler s_defaultTaskScheduler = (TaskScheduler) new ThreadPoolTaskScheduler();

...

}

目前来看两个方法最大的区别在于TaskCreationOption的不同,一个是DenyChildAttach,另一个是None

接着往下看InternalStartNew

internal static Task InternalStartNew(Task creatingTask, Delegate action, object state, CancellationToken cancellationToken, TaskScheduler scheduler, TaskCreationOptions options, InternalTaskOptions internalOptions, ref StackCrawlMark stackMark)

{

if (scheduler == null)

throw new ArgumentNullException("scheduler");

Task task = new Task(action, state, creatingTask, cancellationToken, options, internalOptions | InternalTaskOptions.QueuedByRuntime, scheduler);

task.PossiblyCaptureContext(ref stackMark);

task.ScheduleAndStart(false);

return task;

}

首先实例化一个Task:

internal Task(Delegate action, object state, Task parent, CancellationToken cancellationToken, TaskCreationOptions creationOptions, InternalTaskOptions internalOptions, TaskScheduler scheduler)

{

if (action == null)

throw new ArgumentNullException("action");

if ((creationOptions & TaskCreationOptions.AttachedToParent) != TaskCreationOptions.None || (internalOptions & InternalTaskOptions.SelfReplicating) != InternalTaskOptions.None)

this.m_parent = parent;

this.TaskConstructorCore((object) action, state, cancellationToken, creationOptions, internalOptions, scheduler);

}

如果option是AttachToParent,那么internalCurrent就赋值给m_parent,目前为null,SelfReplicating是用来做并行计算的,会在TPL里详解。随后调用TaskConstructorCore

internal void TaskConstructorCore(object action, object state, CancellationToken cancellationToken, TaskCreationOptions creationOptions, InternalTaskOptions internalOptions, TaskScheduler scheduler)

{

this.m_action = action;

this.m_stateObject = state;

this.m_taskScheduler = scheduler;

if ((creationOptions & ~(TaskCreationOptions.PreferFairness | TaskCreationOptions.LongRunning | TaskCreationOptions.AttachedToParent | TaskCreationOptions.DenyChildAttach | TaskCreationOptions.HideScheduler | TaskCreationOptions.RunContinuationsAsynchronously)) != TaskCreationOptions.None)

throw new ArgumentOutOfRangeException("creationOptions");

if ((creationOptions & TaskCreationOptions.LongRunning) != TaskCreationOptions.None && (internalOptions & InternalTaskOptions.SelfReplicating) != InternalTaskOptions.None)

throw new InvalidOperationException(Environment.GetResourceString("Task_ctor_LRandSR"));

int num = (int) (creationOptions | (TaskCreationOptions) internalOptions);

if (this.m_action == null || (internalOptions & InternalTaskOptions.ContinuationTask) != InternalTaskOptions.None)

num |= 33554432;

this.m_stateFlags = num;

if (this.m_parent != null && (creationOptions & TaskCreationOptions.AttachedToParent) != TaskCreationOptions.None && (this.m_parent.CreationOptions & TaskCreationOptions.DenyChildAttach) == TaskCreationOptions.None)

this.m_parent.AddNewChild();

if (!cancellationToken.CanBeCanceled)

return;

this.AssignCancellationToken(cancellationToken, (Task) null, (TaskContinuation) null);

}

如果options不为DenyChildAttach而且m_parent不为空,则把当前task作为child添加到m_parent。也就是说Task.Run不允许把要执行的task作为当前task的child。

Task已创建,接着调用PossiblyCaptureContext来获取execution context。

internal static ExecutionContext Capture(ref StackCrawlMark stackMark, ExecutionContext.CaptureOptions options)

{

ExecutionContext.Reader executionContextReader = Thread.CurrentThread.GetExecutionContextReader();

if (executionContextReader.IsFlowSuppressed)

return (ExecutionContext) null;

SecurityContext securityContext = SecurityContext.Capture(executionContextReader, ref stackMark);

HostExecutionContext executionContext1 = HostExecutionContextManager.CaptureHostExecutionContext();

SynchronizationContext synchronizationContext = (SynchronizationContext) null;

LogicalCallContext logicalCallContext = (LogicalCallContext) null;

if (!executionContextReader.IsNull)

{

if ((options & ExecutionContext.CaptureOptions.IgnoreSyncCtx) == ExecutionContext.CaptureOptions.None)

synchronizationContext = executionContextReader.SynchronizationContext == null ? (SynchronizationContext) null : executionContextReader.SynchronizationContext.CreateCopy();

if (executionContextReader.LogicalCallContext.HasInfo)

logicalCallContext = executionContextReader.LogicalCallContext.Clone();

}

Dictionary<IAsyncLocal, object> dictionary = (Dictionary<IAsyncLocal, object>) null;

List<IAsyncLocal> asyncLocalList = (List<IAsyncLocal>) null;

if (!executionContextReader.IsNull)

{

dictionary = executionContextReader.DangerousGetRawExecutionContext()._localValues;

asyncLocalList = executionContextReader.DangerousGetRawExecutionContext()._localChangeNotifications;

}

if ((options & ExecutionContext.CaptureOptions.OptimizeDefaultCase) != ExecutionContext.CaptureOptions.None && securityContext == null && (executionContext1 == null && synchronizationContext == null) && ((logicalCallContext == null || !logicalCallContext.HasInfo) && (dictionary == null && asyncLocalList == null)))

return ExecutionContext.s_dummyDefaultEC;

ExecutionContext executionContext2 = new ExecutionContext();

executionContext2.SecurityContext = securityContext;

if (executionContext2.SecurityContext != null)

executionContext2.SecurityContext.ExecutionContext = executionContext2;

executionContext2._hostExecutionContext = executionContext1;

executionContext2._syncContext = synchronizationContext;

executionContext2.LogicalCallContext = logicalCallContext;

executionContext2._localValues = dictionary;

executionContext2._localChangeNotifications = asyncLocalList;

executionContext2.isNewCapture = true;

return executionContext2;

}

ExecutionContext包含了SecurityContext,SynchronizationContext以及LogicalCallContext,其中SynchronizationContext需要做CreateCopy,LogicalCallContext需要做clone,所有这一切都是用户态的,不涉及内核,性能棒棒哒!

接着调用ScheduleAndStart:

internal void ScheduleAndStart(bool needsProtection)

{

if (needsProtection)

{

if (!this.MarkStarted())

return;

}

else

this.m_stateFlags = this.m_stateFlags | 65536;

if (Task.s_asyncDebuggingEnabled)

Task.AddToActiveTasks(this);

if (AsyncCausalityTracer.LoggingOn && (this.Options & (TaskCreationOptions) 512) == TaskCreationOptions.None)

AsyncCausalityTracer.TraceOperationCreation(CausalityTraceLevel.Required, this.Id, "Task: " + ((Delegate) this.m_action).Method.Name, 0UL);

try

{

this.m_taskScheduler.InternalQueueTask(this);

}

catch (ThreadAbortException ex)

{

this.AddException((object) ex);

this.FinishThreadAbortedTask(true, false);

}

catch (System.Exception ex)

{

TaskSchedulerException schedulerException = new TaskSchedulerException(ex);

this.AddException((object) schedulerException);

this.Finish(false);

if ((this.Options & (TaskCreationOptions) 512) == TaskCreationOptions.None)

this.m_contingentProperties.m_exceptionsHolder.MarkAsHandled(false);

throw schedulerException;

}

}

 

internal void InternalQueueTask(Task task)

{

task.FireTaskScheduledIfNeeded(this);

this.QueueTask(task);

}

FireTaskScheduledIfNeeded判断是否开启EWT Trace,接着调用ThreadPoolTaskScheduler.QueueTask


private static readonly ParameterizedThreadStart s_longRunningThreadWork = new ParameterizedThreadStart(ThreadPoolTaskScheduler.LongRunningThreadWork);

private static void LongRunningThreadWork(object obj)

{

(obj as Task).ExecuteEntry(false);

}

protected internal override void QueueTask(Task task)

{

if ((task.Options & TaskCreationOptions.LongRunning) != TaskCreationOptions.None)

{

new Thread(ThreadPoolTaskScheduler.s_longRunningThreadWork)

{

IsBackground = true

}.Start((object) task);

}

else

{

bool forceGlobal = (uint) (task.Options & TaskCreationOptions.PreferFairness) > 0U;

ThreadPool.UnsafeQueueCustomWorkItem((IThreadPoolWorkItem) task, forceGlobal);

}

}



如果options是LongRunning,那么单独创建一个线程执行该任务(ExecuteEntry),否则就调用ThreadPool.UnsafeQueueCustomWorkItem,这个方法我们熟,还记得在.net线程池内幕里有讲到的global work queue和local work queue吗?给ThreadPool添加一个任务实际上是在global work queue添加一个任务,而task就是往local work queue里添加任务。

ThreadPoolWorkQueue源码:

public void Enqueue(IThreadPoolWorkItem callback, bool forceGlobal)

{

ThreadPoolWorkQueueThreadLocals queueThreadLocals = (ThreadPoolWorkQueueThreadLocals) null;

if (!forceGlobal)

queueThreadLocals = ThreadPoolWorkQueueThreadLocals.threadLocals;

if (this.loggingEnabled)

FrameworkEventSource.Log.ThreadPoolEnqueueWorkObject((object) callback);

if (queueThreadLocals != null)

{

queueThreadLocals.workStealingQueue.LocalPush(callback);

}

else

{

ThreadPoolWorkQueue.QueueSegment comparand = this.queueHead;

while (!comparand.TryEnqueue(callback))

{

Interlocked.CompareExchange<ThreadPoolWorkQueue.QueueSegment>(ref comparand.Next, new ThreadPoolWorkQueue.QueueSegment(), (ThreadPoolWorkQueue.QueueSegment) null);

for (; comparand.Next != null; comparand = this.queueHead)

Interlocked.CompareExchange<ThreadPoolWorkQueue.QueueSegment>(ref this.queueHead, comparand.Next, comparand);

}

}

this.EnsureThreadRequested();

}

由于线程已经执行过任务(global的也有可能是local的),所以代码会走到queueThreadLocals.workStealingQueue.LocalPush(callback)


internal volatile IThreadPoolWorkItem[] m_array = new IThreadPoolWorkItem[32];

private SpinLock m_foreignLock = new SpinLock(false);

public void LocalPush(IThreadPoolWorkItem obj)

{

int num1 = this.m_tailIndex;

if (num1 == int.MaxValue)

{

bool lockTaken = false;

try

{

this.m_foreignLock.Enter(ref lockTaken);

if (this.m_tailIndex == int.MaxValue)

{

this.m_headIndex = this.m_headIndex & this.m_mask;

this.m_tailIndex = num1 = this.m_tailIndex & this.m_mask;

}

}

finally

{

if (lockTaken)

this.m_foreignLock.Exit(true);

}

}

if (num1 < this.m_headIndex + this.m_mask)

{

Volatile.Write<IThreadPoolWorkItem>(ref this.m_array[num1 & this.m_mask], obj);

this.m_tailIndex = num1 + 1;

}

else

{

bool lockTaken = false;

try

{

this.m_foreignLock.Enter(ref lockTaken);

int num2 = this.m_headIndex;

int num3 = this.m_tailIndex - this.m_headIndex;

if (num3 >= this.m_mask)

{

IThreadPoolWorkItem[] threadPoolWorkItemArray = new IThreadPoolWorkItem[this.m_array.Length << 1];

for (int index = 0; index < this.m_array.Length; ++index)

threadPoolWorkItemArray[index] = this.m_array[index + num2 & this.m_mask];

this.m_array = threadPoolWorkItemArray;

this.m_headIndex = 0;

this.m_tailIndex = num1 = num3;

this.m_mask = this.m_mask << 1 | 1;

}

Volatile.Write<IThreadPoolWorkItem>(ref this.m_array[num1 & this.m_mask], obj);

this.m_tailIndex = num1 + 1;

}

finally

{

if (lockTaken)

this.m_foreignLock.Exit(false);

}

}

}

Local work queue(m_array)首先被限死为32,如果queue超过最大数了,则扩大为原来的2倍,以此类推。这里也使用了自旋锁和内存写屏障来代替同步锁提高性能。

 

至此,task已被创建好,并加入到了ThreadPool的local work queue。那么task是如何被调度的呢?为什么LongRunning就要单独起一个线程去做?请听下回分解!


原文地址: http://www.cnblogs.com/newbier/p/6203422.html


.NET社区新闻,深度好文,微信中搜索dotNET跨平台或扫描二维码关注

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/327361.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux(笔记)

开启端口时&#xff0c;宝塔面板和阿里云都要开启 简介 我们为什么要学习Linux linux诞生了这么多年&#xff0c;以前还喊着如何能取代windows系统&#xff0c;现在这个口号已经小多了&#xff0c;任何事物发展都有其局限性都有其天花板。就如同在国内再搞一个社交软件取代腾讯…

自增主键与UUID的优缺点

https://blog.csdn.net/rocling/article/details/83116950 自增主键与UUID的优缺点 rocling 2018-10-17 20:15:02 8062 收藏 8 分类专栏&#xff1a; sql java 文章标签&#xff1a; sql 版权 自增主键 自增ID是在设计表时将id字段的值设置为自增的形式&#xff0c;这样当…

Git 在团队中的最佳实践--如何正确使用Git Flow

我们已经从SVN 切换到Git很多年了&#xff0c;现在几乎所有的项目都在使用Github管理, 本篇文章讲一下为什么使用Git, 以及如何在团队中正确使用。 Git的优点 Git的优点很多&#xff0c;但是这里只列出我认为非常突出的几点。 由于是分布式&#xff0c;所有本地库包含了远程库的…

拿到阿里巴巴的实习生Offer就是这么简单

转载自 拿到阿里巴巴的实习生Offer就是这么简单 一、个人简介及面试流程简介 本人本科就读于一个非985非211的一本高校&#xff0c;研究生就读于某985高校&#xff0c;目前研究方向为云计算与大数据。实习内推部门为菜鸟网络&#xff0c;所投的岗位为后台开发Java(包含大数据…

[译] RESTful API 设计最佳实践

https://juejin.im/entry/6844903503953920007 [译] RESTful API 设计最佳实践 阅读 8779 收藏 0 2017-10-16 原文链接&#xff1a; segmentfault.com 原文&#xff1a;RESTful API Design. Best Practices in a Nutshell. 作者&#xff1a;Philipp Hauer 项目资源的URL应该…

Git(笔记)

版本控制 版本控制&#xff08;Revision control&#xff09;是一种在开发的过程中用于管理我们对文件、目录或工程等内容的修改历史&#xff0c;方便查看更改历史记录&#xff0c;备份以便恢复以前的版本的软件工程技术。 实现跨区域多人协同开发追踪和记载一个或者多个文件…

做到我这样,你也能拿到京东Offer

转载自 做到我这样&#xff0c;你也能拿到京东Offer 最近&#xff0c;春招已经基本接近尾声了&#xff0c;我找了几位拿到名企Offer的粉丝&#xff0c;请他们总结了面试经验&#xff0c;近期会分批的推送给大家。希望给那些正在准备秋招的同学提供些帮助。这是一篇7000字的长文…

迁移传统.net 应用到.net core [视频]

.net core是.NET技术的未来&#xff0c;这一点正在被越来越多的公司认识到&#xff0c;但是如何将传统的.NET应用迁移到.NET Core是一个迫切需要解决的问题。 对于传统.NET应用来说&#xff0c;使用和不使用.NET Core可能并不能直接给企业带来好处&#xff0c;相反使用中遇到了…

POI

Poi&#xff08;适合小数据量&#xff09; Apache POI 官网&#xff1a;https://poi.apache.org/ POI是Apache软件基金会的&#xff0c;POI为“Poor Obfuscation Implementation”的首字母缩写&#xff0c;意为“简洁版的模糊实现”。 所以POI的主要功能是可以用Java操作Micr…

因 Redis Key 命令不规范,导致熬了一个通宵才把Key删完了!

https://mp.weixin.qq.com/s/7FL0nUTk6aFmAb2J__5Mtw 因 Redis Key 命令不规范&#xff0c;导致熬了一个通宵才把Key删完了&#xff01; 点击关注 &#x1f449; Java面试那些事儿 9月3日 # 前言 由于有一条业务线不理想&#xff0c;高层决定下架业务。对于我们技术团队而言&a…

使用 CefSharp 在 C# App 中嵌入 Chrome 浏览器

介绍 以前曾试过在app中整合一个可靠又快速的web浏览器吗&#xff1f; 在本文中&#xff0c;你会学到如何轻松地将奇妙的CefSharp网页浏览器组件&#xff08;基于Chromium&#xff09;集成到你的C# app中。 然后&#xff0c;你可以使用此web浏览器&#xff1a; 给用户提供一个集…

从入门到熟悉 HTTPS 的 9 个问题

转载自 从入门到熟悉 HTTPS 的 9 个问题 Q1: 什么是 HTTPS&#xff1f; BS: HTTPS 是安全的 HTTP HTTP 协议中的内容都是明文传输&#xff0c;HTTPS 的目的是将这些内容加密&#xff0c;确保信息传输安全。最后一个字母 S 指的是 SSL/TLS 协议&#xff0c;它位于 HTTP 协议…

Jexus针对Asp.net core应用程序的六大不可替代的优势

Jexus 是一款运行于 Linux 平台&#xff0c;以支持 ASP.NET、PHP 为特色的集高安全性和高性能为一体的 WEB 服务器和反向代理服务器。 1&#xff0c;配置简便&#xff1a;在Jexus上&#xff0c;Asp.net core只是Jexus上的一个“站点”&#xff0c;因此&#xff0c;只需在Jexus…

安装docker遇到的坑 Could not resolve host: download.docker.com;

我写的 1.编辑网卡 vim /etc/sysconfig/network-scripts/ifcfg-ens33 2.增加这三行 DNS18.8.8.8 DNS2114.114.114.114 PEERDNSno 3.最后重启网络service network restart 即可。不行就重启虚拟机 4.设置稳定的源yum-config-manager \ --add-repo \ https://download…

EasyExcel(笔记)

常用场景 1、将用户信息导出为excel表格&#xff08;导出数据…&#xff09; 2、将Excel表中的信息录入到网站数据库&#xff08;习题上传…&#xff09; 开发中经常会设计到excel的处理&#xff0c;如导出Excel&#xff0c;导入Excel到数据库中&#xff01; 操作Excel目前比…

细说Redis监控和告警

对于任何应用服务和组件&#xff0c;都需要一套完善可靠谱监控方案。尤其redis这类敏感的纯内存、高并发和低延时的服务&#xff0c;一套完善的监控告警方案&#xff0c;是精细化运营的前提。本文分几节&#xff0c;细说Redis的监控和告警&#xff1a;1.Redis监控告警的价值2.R…

curl和wget的区别和使用

https://www.cnblogs.com/wyaokai/p/11947379.html https://blog.csdn.net/IT_hejinrong/article/details/79361095 curl和wget的区别和使用 curl和wget基础功能有诸多重叠&#xff0c;如下载等。 非要说区别的话&#xff0c;curl由于可自定义各种请求参数所以在模拟web请求…

vmware启动多个虚拟机

启动顺序 这样4个虚拟机就启动了 也够使用了 (如果出现某个虚拟机不能启动貌似多重复全套流程就可以了) CentOS 64 位5swarm20201006_3_配合第三章register CentOS 64 位6 CentOS 64 位5swarm20201007_5_第三章学完的镜像 CentOS 64 位5swarm20200927 0&#xff09;关闭…

怎样在Redis通过StackExchange.Redis 存储集合类型List

StackExchange 是由StackOverFlow出品&#xff0c; 是对Redis的.NET封装&#xff0c;被越来越多的.NET开发者使用在项目中。绝大部分原先使用ServiceStack的开发者逐渐都转了过来&#xff0c;由于SS在其新版中不再开源&#xff0c;并对免费版本有所限制。 实际问题 那么用.NET的…

Java爬虫小测---ElasticSearch

项目搭建 1、启动ES&#xff0c;和head-master&#xff0c;用head-master建立索引 不建立也没事&#xff0c;添加数据的时候会自动创建 2、导入SpringBoot需要的依赖 注意&#xff1a;elasticsearch的版本要和自己本地的版本一致&#xff01;所以还要在pom里面添加自定义版本…