Почему мой TaskScheduler заменяется на стандартный по умолчанию после некоторых ожидающих вызовов? - PullRequest
2 голосов
/ 24 марта 2019

Я пытаюсь установить пользовательский TaskScheduler, но он не работает для вложенных асинхронных методов.Планировщик загадочно меняется на значение по умолчанию.Вот код:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;

namespace MyApp
{
    internal static class Program
    {
        public static void Main(string[] args)
        {
            var scheduler = new MyTaskScheduler();
            var context = new MySynchronizationContext(scheduler);
            context.Post(Run, null);
            Thread.Sleep(3000);
        }

        private static void Run(object state)
        {
            RunAsync(); // no await here, intentional.
        }

        private static async Task RunAsync()
        {
            Logger.WriteLine("Before InternalRunAsync"); // <-- Scheduler OK.
            await InternalRunAsync();
            Logger.WriteLine("After InternalRunAsync"); // <-- Scheduler changed. WHY???
        }

        private static async Task InternalRunAsync()
        {
            Logger.WriteLine("Before Delay"); // <-- Scheduler OK.
            await Task.Delay(TimeSpan.FromSeconds(1.5));
            Logger.WriteLine("After Delay"); // <-- Scheduler OK.
        }
    }

    internal static class Logger
    {
        private static readonly Stopwatch sw = Stopwatch.StartNew();

        public static void WriteLine(string msg)
        {
            var nativeThreadId = AppDomain.GetCurrentThreadId();
            var threadId = Thread.CurrentThread.ManagedThreadId;
            var syncContext = SynchronizationContext.Current?.ToString() ?? "null";
            var scheduler = TaskScheduler.Current.ToString();
            Console.WriteLine(
                $"[{sw.Elapsed.TotalSeconds} native:{nativeThreadId} managed:{threadId} syncCtx:{syncContext} sch:{scheduler}] {msg}");
        }
    }

    internal sealed class MySynchronizationContext : SynchronizationContext
    {
        private readonly MyTaskScheduler scheduler;

        public MySynchronizationContext(MyTaskScheduler scheduler)
        {
            this.scheduler = scheduler;
        }

        public override SynchronizationContext CreateCopy()
        {
            return new MySynchronizationContext(scheduler);
        }

        public override void Post(SendOrPostCallback d, object state)
        {
            var caller = new SetContextWrapper(this, d, state);
            new Task(caller.Run).Start(scheduler);
        }

        public override void Send(SendOrPostCallback d, object state)
        {
            var caller = new SetContextWrapper(this, d, state);
            new Task(caller.Run).RunSynchronously(scheduler);
        }

        private sealed class SetContextWrapper
        {
            private readonly MySynchronizationContext _context;
            private readonly SendOrPostCallback _callback;
            private readonly object _state;

            public SetContextWrapper(MySynchronizationContext context, SendOrPostCallback callback, object state)
            {
                _context = context;
                _callback = callback;
                _state = state;
            }

            public void Run()
            {
                SynchronizationContext.SetSynchronizationContext(_context);
                _callback(_state);
            }
        }
    }

    internal sealed class MyTaskScheduler : TaskScheduler
    {
        protected override void QueueTask(Task task)
        {
            ThreadPool.QueueUserWorkItem(RunTask, task);
        }

        private void RunTask(object state)
        {
            var task = (Task) state;
            TryExecuteTask(task);
        }

        protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
        {
            return TryExecuteTask(task);
        }

        protected override IEnumerable<Task> GetScheduledTasks()
        {
            return new List<Task>();
        }
    }
}

Вывод:

[0.0003366 native:13400 managed:3 syncCtx:MyApp.MySynchronizationContext sch:MyApp.MyTaskScheduler] Before InternalRunAsync
[0.0016017 native:13400 managed:3 syncCtx:MyApp.MySynchronizationContext sch:MyApp.MyTaskScheduler] Before Delay
[1.5056582 native:13400 managed:3 syncCtx:MyApp.MySynchronizationContext sch:MyApp.MyTaskScheduler] After Delay
[1.5059042 native:13400 managed:3 syncCtx:MyApp.MySynchronizationContext sch:System.Threading.Tasks.ThreadPoolTaskScheduler] After InternalRunAsync

Обратите внимание на последнюю строку.Это показывает, что после возврата метода InternalRunAsync планировщик был сброшен до ThreadPoolTaskScheduler.Почему?

...