Task启动方式及源码探究

启动Task有几种方式:

1.Task.Run()

2.new TaskFactory.StartNew()

3.var t=new Task();  t.start();

平时用的最多是第一和第二种,那么他们之间有什么差异?接下来通过两个demo进行说明。

        static void Main(string[] args)
        {
            for (int i = 0; i < 5; i++)
            {
                 Task.Run(() => Sayhi("task"));
                 new TaskFactory().StartNew(() => Sayhi("taskfactory"));
            }
        
            Console.ReadLine();
        } 


        public static void Sayhi(string method)
        {
            Console.WriteLine(method+":"+Thread.CurrentThread.ManagedThreadId);
        }

运行结果:

task:5
taskfactory:4
taskfactory:6
task:7
task:5
task:6
taskfactory:7
taskfactory:5
task:4
taskfactory:6

 

通过以上结果可以猜测task跟taskfactory运行的模式基本一致,线程重复使用,猜测是通过线程池来调度

接下来,我给TaskFactory().StartNew添加一个参数TaskCreationOptions.LongRunning

 new TaskFactory().StartNew(() => Sayhi("taskfactory"),TaskCreationOptions.LongRunning);

运行结果:

task:9
task:9
task:4
taskfactory:5
task:6
task:7
taskfactory:8
taskfactory:10
taskfactory:11
taskfactory:12

如果你眼神犀利,相信已经看出其中的差异。没错,taskfactory每次运行都是一个新的线程,由此可以猜测TaskCreationOptions.LongRunning这个参数会决定task运行的命运,它将不会进入线程池,自己单飞了。

综上所述,有以下猜测:

1.Task.Run()与TaskFactory().StartNew()运行模式一致

2.都是线程池调度

3.TaskCreationOptions=TaskCreationOptions.LongRunning时,不由线程池调度。

 

为了验证猜测,让我们来翻一下源码。

Task.Run()

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

 new TaskFactory().StartNew()

   public Task StartNew(Action action)
        {
            StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
            Task currTask = Task.InternalCurrent;
            return Task.InternalStartNew(currTask, action, null, m_defaultCancellationToken, GetDefaultScheduler(currTask),
                m_defaultCreationOptions, InternalTaskOptions.None, ref stackMark);
        }

 

可以看到,无论是Task.Run()还是new TaskFactory().StartNew()都是调用了Task.InternalStartNew方法,而Task.Run是默认的参数,TaskFactory().StartNew()拥有更多的自由度。由此验证猜测1。

继续跳进,最终调用ScheduleAndStart方法

 internal void ScheduleAndStart(bool needsProtection)
        { 
            try
            {
                // Queue to the indicated scheduler.
                m_taskScheduler.InternalQueueTask(this); //调度任务
            }
            catch (ThreadAbortException tae)
            {
                AddException(tae);
                FinishThreadAbortedTask(true, false);
            }
       }
m_taskScheduler实例就是上面的TaskScheduler.Default,来瞧瞧是装了什么东西
 private static readonly TaskScheduler s_defaultTaskScheduler = new ThreadPoolTaskScheduler();

 public static TaskScheduler Default 
        {
            get
            {
                return s_defaultTaskScheduler;
            }
        }
ThreadPoolTaskScheduler,瞧瞧这单词是不是有内味了,来,答案快水落石出了。
   protected internal override void QueueTask(Task task)
        {
            if ((task.Options & TaskCreationOptions.LongRunning) != 0)
            {
                // Run LongRunning tasks on their own dedicated thread.
                Thread thread = new Thread(s_longRunningThreadWork);
                thread.IsBackground = true; // Keep this thread from blocking process shutdown
                thread.Start(task);
            }
            else
            {
                // Normal handling for non-LongRunning tasks.
                bool forceToGlobalQueue = ((task.Options & TaskCreationOptions.PreferFairness) != 0);
                ThreadPool.UnsafeQueueCustomWorkItem(task, forceToGlobalQueue);  
            }
        }

看到这里相信不需要我再说什么了... 猜测验证成功。

总结以下:

1.Task.Run()是TaskFactory().StartNew()的其中一种形式。TaskFactory().StartNew()拥有更多自由度。

2.Task.Run()是线程池调度,TaskFactory().StartNew()参数TaskCreationOptions=TaskCreationOptions.LongRunning时,不由线程池调度。

 

源码链接:https://referencesource.microsoft.com/#mscorlib/system/threading/Tasks/Task.cs

 

 

 

posted @ 2020-04-20 15:41  海底下呼吸  阅读(630)  评论(0编辑  收藏  举报