2015-08-26 2 views
20

Я прочел this question от Noseratio, который показывает поведение, где TaskScheduler.Current не такой же после того, как он уже закончил свою работу.Ожидание не возобновляет контекст после операции async?

Ответ гласит:

Если нет актуальной задачей выполняется, то TaskScheduler.Current такая же, как TaskScheduler.Default

Что верно. Я уже видел это here:

  • TaskScheduler.Default
    • Возвращает экземпляр ThreadPoolTaskScheduler
  • TaskScheduler.Current
    • Если вызывается из выполняющегося задания будет возвращать TaskScheduler из в настоящее время выполнения задачи
    • Если вызывается из любого другого места вернутся TaskScheduler.Default

Но потом я подумал, если так, давайте сделать создать фактическое Task (а не только Task.Yield()) и протестируйте его:

async void button1_Click_1(object sender, EventArgs e) 
{ 
    var ts = TaskScheduler.FromCurrentSynchronizationContext(); 
    await Task.Factory.StartNew(async() => 
    { 
     MessageBox.Show((TaskScheduler.Current == ts).ToString()); //True 

      await new WebClient().DownloadStringTaskAsync("http://www.google.com"); 

     MessageBox.Show((TaskScheduler.Current == ts).ToString());//False 

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

Первый информационный блок «True», se конд является "False"

Вопрос:

Как вы можете видеть, я создал реальную задачу.

Я могу понять, почему первый MessageBox дает True. То becuase из:

Если вызывается из выполняющегося задания будет возвращать TaskScheduler в исполняемой в данный момент задачи

И эта задача действительно есть ts, который является отправленным TaskScheduler.FromCurrentSynchronizationContext()

Но почему контекст не сохранен на второй MessageBox? Для меня это было непонятно из ответа Стефана.

Дополнительная информация:

Если я пишу, вместо (второго MessageBox):

MessageBox.Show((TaskScheduler.Current == TaskScheduler.Default).ToString()); 

Это выход true. Но почему ?

+0

Отличный вопрос, но что, если 'task scheduler! = Контекст синхронизации'? – AgentFire

+0

Хороший случай для жесткого правила: никогда не принимайте 'TaskScheduler.Текущий' то, что вы думаете, это :) Гарантируется, что вы передали 'Factory.StartNew' для области действия задачи лямбда (которая является' Func 'в вашем случае и возвращается, когда она попадает в первый' await'). Любое другое поведение следует рассматривать как детали реализации. – Noseratio

ответ

10

Причины путаницы таковы:

  1. Пользовательский интерфейс не имеет «специальный» TaskScheduler. Случай по умолчанию код, выполняемые в потоке пользовательского интерфейса является то, что TaskScheduler.Current хранит ThreadPoolTaskScheduler и SynchronizationContext.Current магазинов WindowsFormsSynchronizationContext (или соответствующие одному в других приложениях UI)
  2. ThreadPoolTaskScheduler в TaskScheduler.Current не обязательно означает, что это TaskScheduler используются для запуска тока кусок кода. Это также означает TaskSchdeuler.Current == TaskScheduler.Default и поэтому «не используется TaskScheduler».
  3. TaskScheduler.FromCurrentSynchronizationContext() не возвращает «acutal» TaskScheduler. Он возвращает «прокси», который отправляет задания прямо на захваченный SynchronizationContext.

Так что, если вы запустите тест перед запуском задачи (или в любом другом месте), вы получите тот же результат, как после ЖДУТ:

MessageBox.Show(TaskScheduler.Current == TaskScheduler.FromCurrentSynchronizationContext()); // False 

Поскольку TaskScheduler.Current является ThreadPoolTaskScheduler и TaskScheduler.FromCurrentSynchronizationContext() возвращает SynchronizationContextTaskScheduler.

Это поток вашего примера:

  • Вы создаете новый SynchronizationContextTaskScheduler из UI-х SynchronizationContext (т.е. WindowsFormsSynchronizationContext).
  • Запланируйте задачу, которую вы создаете, используя Task.Factory.StartNew на этом TaskScheduler. Так как это просто «прокси», он помещает делегата в WindowsFormsSynchronizationContext, который вызывает его в потоке пользовательского интерфейса.
  • Синхронная часть (часть до первого ожидания) этого метода выполняется в потоке пользовательского интерфейса, будучи связанным с SynchronizationContextTaskScheduler.
  • Метод достигает ожидания и «подвешен» при захвате WindowsFormsSynchronizationContext.
  • Когда продолжение возобновлено после ожидания, оно отправляется на это WindowsFormsSynchronizationContext, а не SynchronizationContextTaskScheduler, так как SynchronizationContext s имеют приоритет (this can be seen in Task.SetContinuationForAwait). Затем он регулярно работает на резьбе UI, без каких-либо «специальных» TaskScheduler, поэтому TaskScheduler.Current == TaskScheduler.Default.

Таким образом, создается задание выполняется на прокси TaskScheduler который использует SynchronizationContext, но продолжение после AWAIT размещена на этой SynchronizationContext и не TaskScheduler.

+0

Продолжение после 'await' выполняется на' ThreadPoolTaskScheduler', а не на пользовательском интерфейсе. –

+0

@YuvalItzchakov Почему вы так говорите? Я не согласен. Он отправляется в пользовательский интерфейс с использованием 'SynchronizationContext' – i3arnon

+0

Подождите, о котором вы говорите? :) –

Смежные вопросы