Подтвердить что ты не робот

Понимание поведения TaskScheduler.Current

Здесь простое приложение WinForms:

using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApplication
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private async void button1_Click(object sender, EventArgs e)
        {
            var ts = TaskScheduler.FromCurrentSynchronizationContext();
            await Task.Factory.StartNew(async () =>
            {
                Debug.WriteLine(new
                {
                    where = "1) before await",
                    currentTs = TaskScheduler.Current,
                    thread = Thread.CurrentThread.ManagedThreadId,
                    context = SynchronizationContext.Current
                });

                await Task.Yield(); // or await Task.Delay(1)

                Debug.WriteLine(new
                {
                    where = "2) after await",
                    currentTs = TaskScheduler.Current,
                    thread = Thread.CurrentThread.ManagedThreadId,
                    context = SynchronizationContext.Current
                });

            }, CancellationToken.None, TaskCreationOptions.None, scheduler: ts).Unwrap();
        }
    }
}

Отладка (когда кнопка нажата):

{ where = 1) before await, currentTs = System.Threading.Tasks.SynchronizationContextTaskScheduler, thread = 9, context = System.Windows.Forms.WindowsFormsSynchronizationContext }
{ where = 2) after await, currentTs = System.Threading.Tasks.ThreadPoolTaskScheduler, thread = 9, context = System.Windows.Forms.WindowsFormsSynchronizationContext }

Вопрос: Почему TaskScheduler.Current меняется с SynchronizationContextTaskScheduler на ThreadPoolTaskScheduler после await здесь?

Это по существу показывает поведение TaskCreationOptions.HideScheduler для await продолжения, что, на мой взгляд, неожиданно и нежелательно.

Этот вопрос был вызван другим вопросом:

AspNetSynchronizationContext и ожидайте продолжения в ASP.NET.

4b9b3361

Ответ 1

Если фактическая задача не выполняется, то TaskScheduler.Current совпадает с TaskScheduler.Default. Другими словами, ThreadPoolTaskScheduler фактически действует как как планировщик задач пула потоков, так и значение, означающее "нет текущего планировщика задач".

Первая часть делегата async запланирована явно с помощью SynchronizationContextTaskScheduler и выполняется в потоке пользовательского интерфейса как с планировщиком задач, так и с контекстом синхронизации. Планировщик задач перенаправляет делегат в контекст синхронизации.

Когда await захватывает свой контекст, он захватывает контекст синхронизации (а не планировщик задач) и использует этот syncctx для возобновления. Итак, продолжение метода отправляется на этот syncctx, который выполняет его в потоке пользовательского интерфейса.

Когда продолжение продолжается в потоке пользовательского интерфейса, оно ведет себя очень похоже на обработчик события; делегат выполняется непосредственно, не завернутый в задачу. Если вы проверите TaskScheduler.Current в начале button1_Click, вы также найдете ThreadPoolTaskScheduler.

Кстати, я рекомендую вам относиться к этому поведению (выполнение делегатов напрямую, а не завернутых в задачи) как деталь реализации.