SynchronizationContext Send() должен быть тем же потоком?

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

Сначала я проверяю идентификатор текущей темы

var myThreadId = Thread.CurrentThread.ManagedThreadId;

Я создаю SynchronizationContext и устанавливаю как текущий

var _context = new SynchronizationContext();
SynchronizationContext.SetSynchronizationContext(_context);

Затем я отправляю некоторые действия в контекст (мы сейчас находимся в другом потоке)

_context.Send(x => _action(sender, e), null);

Внутри этого действия я снова проверяю ThreadId

Assert.Equal(myThreadId, Thread.CurrentThread.ManagedThreadId);

Это не удается. Разве я не должен быть снова в моей первоначальной теме?

2 ответа

Решение

Создание нового SynchronizationContext и используя Send или же Post точно так же, как синхронный вызов делегата, как если бы вы делали это самостоятельно. Код довольно прост (взят из источника):

public virtual void Send(SendOrPostCallback d, Object state)
{
    d(state);
}

Вы пытаетесь имитировать действие пользовательских контекстов, таких как DispatcherSynchronizationContext например, который знает о потоке цикла сообщений пользовательского интерфейса WPF. Такое поведение не происходит здесь.

Если вы пришли из потока пользовательского интерфейса, вам нужно захватить контекст и передать его.

Вы можете увидеть это более четко внутри DispatcherSynchronizationContext какие очереди работают с пользовательским интерфейсом, используя Dispatcher учебный класс:

/// <summary>
///     Synchronously invoke the callback in the SynchronizationContext.
/// </summary>
public override void Send(SendOrPostCallback d, Object state)
{
    // Call the Invoke overload that preserves the behavior of passing
    // exceptions to Dispatcher.UnhandledException.  
    if(BaseCompatibilityPreferences.GetInlineDispatcherSynchronizationContextSend() && 
       _dispatcher.CheckAccess())
    {
        // Same-thread, use send priority to avoid any reentrancy.
        _dispatcher.Invoke(DispatcherPriority.Send, d, state);
    }
    else
    {
        // Cross-thread, use the cached priority.
        _dispatcher.Invoke(_priority, d, state);
    }
}

Если вы создаете новый SynchronizationContext, он всегда будет оборачивать пул потоков и никогда не выполнится Send или же Post в потоке пользовательского интерфейса.

Из MSDN;

Класс SynchronizationContext является базовым классом, который предоставляет контекст с бесплатными потоками без синхронизации.

Например;

void Button_Click(object sender, EventArgs e)
{
     var context = SynchronizationContext.Current;

     // this is executred on the UI thread.
     context.Send(() =>
     {
           // this is also executed on the UI thread.
     });

     Task.Run(() =>
     {
         // this is executed on a worker thread
         context.Send(() =>
         {
             // this is still executed on the UI thread!
         });
     }

     // what you are doing will always execute on a worker thread.
     var  myNewContext = new SynchronizationContext();
     SynchronizationContext.SetSynchronizationContext(myNewContext);

     myNewContext.Send(() =>
     {
         // this will run on a worker thread.
     }         
}

Дальнейшее чтение

Параллельные вычисления - это все о SynchronizationContext

Другие вопросы по тегам