Async函数:await后的代码段由哪个线程执行?

阅读本篇文章前需要C#多线程基础、Async函数基础、SynchronizationContext基础

 

默认情况

以下是测试代码:

 
        static void Main(string[] args)
        {
            Test0();

            Console.Read();
        }

        static void Test0()
        {
            AsyncMethod();
            
            Console.WriteLine("after calling AsyncMethod : " + Thread.CurrentThread.ManagedThreadId);
        }

        static async Task AsyncMethod()
        {
            Console.WriteLine("before await : " + Thread.CurrentThread.ManagedThreadId);
            await Task.Run(() =>
            {
                // 一个耗时的计算
                ConsumeTime();
                Console.WriteLine("Task : " + Thread.CurrentThread.ManagedThreadId);
            });
            Console.WriteLine("after await : " + Thread.CurrentThread.ManagedThreadId);
        }

执行结果:

Async函数:await后的代码段由哪个线程执行?_第1张图片

可以看出,await后面的代码段是由执行Task的线程来执行的。

但资料上的理论知识还表示,由于在Task执行完之前,Thread1会挂起状态机返回执行Console.WriteLine("after calling AsyncMethod : " + Thread.CurrentThread.ManagedThreadId);,等到Task结束后,由Thread3恢复状态机,执行await后的代码,这种挂起操作是有消耗的。如果Task很快就执行完了,在Thread1返回之前就已经执行好了,这种情况下,如果还将状态机挂起,就白白损耗了性能。它很聪明,Thread1检测到自己返回前Task已经执行完毕,就不会将状态机挂起,并且由自己直接完成await后的代码,而不是交给Thread3来执行。这种情况我没有写代码检测。
 

 

 

使用TaskCompletionSource的情况

在默认情况下,await后面的代码段是由执行Task的线程来执行的,但当await一个TaskCompletionSource的Task时,由于这个Task并不是真的需要去执行,所以也就没有所谓的执行Task的线程,那么这种情况下谁来执行await后的代码段呢?

以下是测试代码:

        static void Main(string[] args)
        {
            Test1();
            Console.Read();
        }

        static void Test1()
        {
            AsyncTest();
            Task.Run(() =>
            {
                SetResult();
            });
        }

        static async Task AsyncTest()
        {
            Console.WriteLine("before await : " + Thread.CurrentThread.ManagedThreadId);
            m_tcs = new TaskCompletionSource();
            await m_tcs.Task;
            Console.WriteLine("after await : " + Thread.CurrentThread.ManagedThreadId);
            Thread.Sleep(2000);
        }

        static void SetResult()
        {
            Console.WriteLine("before SetResult : " + Thread.CurrentThread.ManagedThreadId);
            m_tcs.SetResult(10);
            Console.WriteLine("after SetResult : " + Thread.CurrentThread.ManagedThreadId);
        }

        private static TaskCompletionSource m_tcs;

执行结果如下:

Async函数:await后的代码段由哪个线程执行?_第2张图片

由此可以看出,使用TaskCompletionSource时,await后的代码段由调用SetResult方法的线程执行。需要注意的是,调用SetResult方法的线程会先转去执行await后的代码,然后再接着执行SetResult后的代码。

 

使用SynchronizationContext的情况

我写了一个SynchronizationContext来让TaskScheduler按照我定的规则来调度Task,当一个线程在await我的SynchronizationContext负责调度的Task时,按理说,我的SynchronizationContext是保证这个Task被调度的一个机制,那么await后的代码段该由谁来执行呢?

下面是我的SynchronizationContext代码,它将需要执行的Task先放到自己的队列里,然后在Tick方法中再去执行这些Task需要执行的操作。


    /// 
    /// 在Tick中调度Task的SynchronizationContext
    /// 
    public class TickBaseSynchronizationContext : SynchronizationContext
    {
        public override void Post(SendOrPostCallback d, object state)
        {
            m_taskQueue.Enqueue(new KeyValuePair(d, state));
        }

        public override void Send(SendOrPostCallback d, object state)
        {
            m_taskQueue.Enqueue(new KeyValuePair(d, state));
        }

        /// 
        /// 驱动task调度
        /// 
        /// 
        public bool Tick()
        {
            KeyValuePair item;
            if (m_taskQueue.TryDequeue(out item))
            {
                try
                {
                    item.Key(item.Value);
                }
                catch (Exception e)
                {
                    EventOnException(e);
                }
                return true;
            }
            return false;
        }

        /// 
        /// 通知使用者调度的task抛出异常
        /// 
        public event Action EventOnException;

        /// 
        /// 调度队列
        /// 
        private ConcurrentQueue> m_taskQueue = new ConcurrentQueue>();
    }

下面是测试代码:

        static void Main(string[] args)
        {
            Test3();

            Console.Read();
        }

        static void Test3()
        {
            Task.Run(() =>
            {
                ExecuteTasks();
            });

            SyncCtxAsyncMethod();

            Console.Read();
        }

        /// 
        /// 调度SynchronizationContext的Task
        /// 
        static void ExecuteTasks()
        {
            while (true)
            {
                if (m_syncCtx != null && !m_syncCtx.Tick())
                {
                    Thread.Sleep(1000);
                }
            }
        }

        /// 
        /// 让SynchronizationContext来调度一个task并await它
        /// 
        static async Task SyncCtxAsyncMethod()
        {
            // 首先设置SynchronizationContext和TaskScheduler
            m_syncCtx = new TickBaseSynchronizationContext();
            SynchronizationContext.SetSynchronizationContext(m_syncCtx);
            m_taskScheduler = TaskScheduler.FromCurrentSynchronizationContext();

            Console.WriteLine("before await : " + Thread.CurrentThread.ManagedThreadId);
            // 这个Task是会被SynchronizationContext调度的
            await Task.Factory.StartNew(() =>
            {
                Console.WriteLine("Task : " + Thread.CurrentThread.ManagedThreadId);
            }, CancellationToken.None, TaskCreationOptions.LongRunning, m_taskScheduler);
            Console.WriteLine("after await : " + Thread.CurrentThread.ManagedThreadId);
        }

        private static TickBaseSynchronizationContext m_syncCtx;

        private static TaskScheduler m_taskScheduler;

结果如下:

Async函数:await后的代码段由哪个线程执行?_第3张图片

 

结果显示,执行Task和执行await后的代码的线程是同一个线程,这其实是一个错误的结论,在这个例子中,SynchronizationContext采用的规则是将所有队列里的Task都使用同一个线程串行调度,这个例子太片面了,可以修改SynchronizationContext的调度规则,比如修改为将队列里的Task都用线程池的线程调度,多写几个不同规则的SynchronizationContext,你会得出正确的结论:await后的代码会封装成一个新的Task由SynchronizationContext按照自己的规则调度执行。

你可能感兴趣的:(C#,-,VS)