0

ли работать Вы задачане Task) как так:Переключение контекста синхронизации означает, что работа будет выполняться в потоке, которому принадлежит контекст синхронизации?

public async void button1_click(...) 
{ 
    await Task.Run(...); 
} 

или использование на старых методов, где вы называете InvokeRequired, чтобы проверить, есть ли необходимость вызова текущей операции на другой контекст синхронизации, а затем вызовите Control.Invoke (например, в случае WinForms), операция выполняется с использованием захваченного контекста синхронизации, если таковой имеется.

Однако, какая из двух вещей означает это?

Если вы запрашиваете задание должно выполняться в потоке пула потоков с использованием любого из методов, будь то старый или новый, это означает, что:

  1. Когда поток получает от земли, переключение контекста синхронизации означает, что он будет ждать, когда поток, которому принадлежит контекст синхронизации, будет выполнять кусок кода? В случае контекста синхронизации, используемого пользовательским интерфейсом, означает ли это, что поток пула потоков отправит ответное действие в очередь сообщений в потоке пользовательского интерфейса и даст?

    ИЛИ

  2. Или это означает, что пул потоков поток будет выполнить действие, но там будет просто переключатель опорной переменной (System.Threading.ExecutionContext.SynchronizationContext), который содержит контекст синхронизации, и в качестве такового, в контексте синхронизации это только кооперативная дисциплина, которую нитки соглашаются придерживаться? Работа по-прежнему будет выполняться потоком пула потоков?

    Конечно, по кооперативной дисциплине *, я не имею в виду, что все будет работать хорошо, даже если беспорядочная нить решила не переключать контекст синхронизации там, где это требовалось. Я имею в виду, что поток, не владеющий контекстом синхронизации, по-прежнему может выполняться, если ссылка на контекст синхронизации изменяется на правую.

От чтения исходного кода AsyncMethodBuilder<TResult>.Start, System.Threading.Tasks.Task.ExecuteWithThreadLocal и System.Threading.ExecutionContext.RunInternal метода, представляется весьма вероятным, что ответ # 2, но я не уверен.

UPDATE

Вот почему и я полагаю, # 2, скорее всего, но я хотел бы исправить.

Если вы просто возьмете приложение Windows Forms и нажмете на него кнопку и запустите код, показанный на картинке в событии клика, вы можете увидеть стек вызовов, который выглядит как мой, как показано на том же снимке ,

enter image description here

Я следовал исходный код каждого метода в стеке вызовов. Я заметил, что переключатель контекста происходит в методе System.Threading.ExecutionContext.RunInternal. Это происходит потому, что метод System.Threading.Tasks.ExecuteWithThreadLocal передает значение true для последнего параметра его вызова методу System.Threading.ExecutionContext.Run. См. line 2823.

Однако после этого вызов продолжается без отправки сообщений в очередь сообщений потока пользовательского интерфейса, и когда он, наконец, достигает метода System.Threading.Tasks.Task<TResult>.InnerInvoke, метод вызывает делегата.

Если ответ №1, если вы могли бы показать мне, где происходит проводка сообщений, я буду прыгать с радостью и сегодня узнаю что-то захватывающее в контексте синхронизации.

Это происходит в методе ExecutionContext.SetExecutionContext?

И если ответ №2, и если бы вы могли подтвердить это, то и я буду петь песню в честь моего открытия.

Side Примечание

Я сделал эту программу, чтобы проверить что-то другое, хотя. Я хотел видеть где переключают контекст синхронизации, если требуется , как:

  1. До достижения await заявление; и
  2. После заявления await, т. е. на ответный вызов продолжения.

И мои выводы удовлетворительно показали мне ответы на вопросы .

Для любого любопытного, переключатель выполнен в Start методы AsyncMethodBuilder «s для любого кода, который перед тем выражения await.

Для кода, который находится после, существует более одного пути. Один из путей изображен в этом стеке вызовов, который показан на рисунке выше.

+1

Ваше предположение неверно, Control.Begin/Invoke() делает ** не ** использовать контекст синхронизации.Это наоборот, это SynchronizationContext, который использует Control.Begin/Invoke(). В частности, экземпляр WindowsFormsSynchronizationContext, который устанавливается автоматически, когда вы создаете элемент управления или вызываете Application.Run(). Ваш вопрос перестает иметь смысл с этой важной деталью. –

+0

@ HansPassant Я думаю о том, что вы сказали. –

+0

@HansPassant Я держу вашу коррекцию в хорошем положении. Мои знания о вещах непростительно ограничены. Я не уверен, как я могу исправить это немедленно и перефразировать мой вопрос. Как и следовало бы, Gobbledygook, суть моего вопроса заключается в следующем: до того, как поток потока потока выполнит действие, если необходимо, чтобы контекст синхронизации был захвачен, это заставляет поток пула потоков отказаться от своего временного фрагмента процессора и делегировать эту работу вернуться к потоку пользовательского интерфейса? Или это означает, что поток пула потоков выполнит действие, предваряющее его с изменением значения ссылочной переменной, которая содержит контекст синхронизации? –

ответ

3

У меня есть async intro blog post, что объясняет, как await работает с SynchronizationContext.Current. В частности, await использует захваченный контекст для резюме метод async.

Таким образом, это не является правильным:

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

Если по «операции», вы имеете в виду ... в коде:

public async void button1_click(...) 
{ 
    await Task.Run(...); 
} 

Что случилось бы, что Task.Run запланирует ... к нити пула потоков (Task.Run всегда использует пул потоков) , Затем await фиксирует текущий SynchronizationContext (в данном случае контекст пользовательского интерфейса) и возвращается.Когда ... завершает работу, то задача, возвращаемая Task.Run, завершится, и button1_click возобновит работу в этом контексте (поток пользовательского интерфейса). Затем он достигает конца метода и возвращается.

В пределах ..., SynchronizationContext.Current будет null. Это продолжение задачи, заданное await, которое использует свой захваченный SynchronizationContext для возобновления потока пользовательского интерфейса.

+0

Спасибо. Мне неинтересно, какой контекст синхронизации ожидает ожидаемая операция или продолжит продолжение. Оба из них, я узнал. Следуя исходному коду метода System.Threading.Tasks.Task.SetContinuationForAwait' (http://referencesource.microsoft.com/#mscorlib/system/threading/Tasks/Task.cs,f4ecbcb6398671fb), я смог установить что продолжение выполнялось в правильном контексте синхронизации. И с этим вопросом (http://stackoverflow.com/q/37635053/303685) я смог узнать, что значение контекста будет внутри делегата потока потока потока. –

+0

Суть моего вопроса заключается в следующем: когда поток потока потока должен запускать действие с использованием контекста синхронизации потока пользовательского интерфейса, например, независимо от того, использует ли TPL или иначе, означает ли это, что поток пула потоков отправит делегат на очереди сообщений в потоке пользовательского интерфейса, чтобы последний выполнил его и предоставил свой срез процессорного времени? Или это означает, что поток пула потоков будет выполнять делегат, но просто изменит ссылочную переменную, которая содержит контекст синхронизации, прежде чем он это сделает? –

+0

BTW, я прочитал эту статью, которую вы связали, и наслаждался ею среди других ваших статей. Я намерен полностью прочитать ваш блог. –

0

Из-за того, что вы читаете еще несколько кода и думаете еще немного, кажется, что ответ №2.

Вот аргументы в пользу этой претензии.

Было бы ужасно дорого, если бы рабочая нить эвакуировала процессор и вызывать контекстный переключатель только потому, что у него не был собственный контекст синхронизации, на котором должна выполняться операция.

Контекст синхронизации - это просто объект, который включает HANDLE в окно или ресурс, на который поток хочет выполнить операцию. Если поток, который в настоящее время выполняется, не имеет этого объекта контекста синхронизации, было бы гораздо более экономным копировать ссылку на контекст синхронизации, который делает собственным дескриптором ресурса и позволяет потоку пула потоков запускать этот контекст. Эта копия должна быть сделана до того, как поток займет свое место на CPU; другими словами, пока он все еще находится в очереди , прежде чем он начнет работу.

Следует также отметить, что во время копирования не создаются новые распределения кучи, так как они также были бы расточительными. Только ссылка на контекст синхронизации владения ресурсом копируется и устанавливается как контекст текущего потока.

Эта моя теория поддерживается следующими фрагментами кода в источнике .NET.

AsyncTaskMethodBuilder<TResult>.Start называет ExecutionContextReader скопировать контекст выполнения, который является владельцем ресурса и когда это будет сделано, он вызывает Undo на ExecutionContextSwitcher, который переворачивает предыдущую операцию.

[SecuritySafeCritical, DebuggerStepThrough, __DynamicallyInvokable] 
public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine: IAsyncStateMachine 
{ 
    if (((TStateMachine) stateMachine) == null) 
    { 
     throw new ArgumentNullException("stateMachine"); 
    } 
    ExecutionContextSwitcher ecsw = new ExecutionContextSwitcher(); 
    RuntimeHelpers.PrepareConstrainedRegions(); 
    try 
    { 
     ExecutionContext.EstablishCopyOnWriteScope(ref ecsw); 
     stateMachine.MoveNext(); 
    } 
    finally 
    { 
     ecsw.Undo(); 
    } 
} 

Метод System.Threading.Tasks.Task.ExecuteWithThreadLocal вызывает ExecutionContext.Run с захваченным контекстом исполнения просит его, чтобы сохранить захваченный контекст выполнения.

Метод Run вызывает ExecutionContext.RunInternal, что делает то же самое: делает копию захваченного контекста и устанавливает его как контекст выполнения текущего потока. И когда это делается, он вызывает метод ExecutionContextSwitcher.Undo, чтобы отменить предыдущую операцию копирования и восстановить исходный контекст выполнения потока.

[SecurityCritical, HandleProcessCorruptedStateExceptions] 
internal static void RunInternal(ExecutionContext executionContext, ContextCallback callback, object state, bool preserveSyncCtx) 
{ 
    if (!executionContext.IsPreAllocatedDefault) 
    { 
     executionContext.isNewCapture = false; 
    } 
    Thread currentThread = Thread.CurrentThread; 
    ExecutionContextSwitcher ecsw = new ExecutionContextSwitcher(); 
    RuntimeHelpers.PrepareConstrainedRegions(); 
    try 
    { 
     Reader executionContextReader = currentThread.GetExecutionContextReader(); 
     if ((executionContextReader.IsNull || executionContextReader.IsDefaultFTContext(preserveSyncCtx)) && ((SecurityContext.CurrentlyInDefaultFTSecurityContext(executionContextReader) && executionContext.IsDefaultFTContext(preserveSyncCtx)) && executionContextReader.HasSameLocalValues(executionContext))) 
     { 
      EstablishCopyOnWriteScope(currentThread, true, ref ecsw); 
     } 
     else 
     { 
      if (executionContext.IsPreAllocatedDefault) 
      { 
       executionContext = new ExecutionContext(); 
      } 
      ecsw = SetExecutionContext(executionContext, preserveSyncCtx); 
     } 
     callback(state); 
    } 
    finally 
    { 
     ecsw.Undo(); 
    } 
} 

Таким образом, никакая работа не отправляется обратно в очередь сообщений потока пользовательского интерфейса. Вместо этого рабочий поток выполняет работу в контексте среды/синхронизации, которому принадлежит дескриптор ресурса.

Это означает, что контекст синхронизации является кооперативной дисциплиной, поскольку он является просто протоколом для копирования объектов кучи, которыми владеет операционная система, к ресурсам, которые поток хочет использовать для его выполнения в течение времени, в течение которого он будет выполняя их.

+0

Я буду обновлять свой ответ результатами моего дальнейшего расследования по этому вопросу. Похоже, что разбирательство не так прямолинейно, как на мой взгляд в настоящее время. –

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