Понимание поведения 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.
Ответы
Ответ 1
Если фактическая задача не выполняется, то TaskScheduler.Current
совпадает с TaskScheduler.Default
. Другими словами, ThreadPoolTaskScheduler
фактически действует как как планировщик задач пула потоков, так и значение, означающее "нет текущего планировщика задач".
Первая часть делегата async
запланирована явно с помощью SynchronizationContextTaskScheduler
и выполняется в потоке пользовательского интерфейса как с планировщиком задач, так и с контекстом синхронизации. Планировщик задач перенаправляет делегат в контекст синхронизации.
Когда await
захватывает свой контекст, он захватывает контекст синхронизации (а не планировщик задач) и использует этот syncctx для возобновления. Итак, продолжение метода отправляется на этот syncctx, который выполняет его в потоке пользовательского интерфейса.
Когда продолжение продолжается в потоке пользовательского интерфейса, оно ведет себя очень похоже на обработчик события; делегат выполняется непосредственно, не завернутый в задачу. Если вы проверите TaskScheduler.Current
в начале button1_Click
, вы также найдете ThreadPoolTaskScheduler
.
Кстати, я рекомендую вам относиться к этому поведению (выполнение делегатов напрямую, а не завернутых в задачи) как деталь реализации.