Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SynchronizationContext Send() supposed to be the same thread?

I have this scenario where I try to handle an event on the same thread as it was created. Which is commonly done in the UiThread, but I'm not on the UiThread to start with. I have some test with basically the following steps. I have left out some details. I am not really sure whether or not this should act as I think it should .

First I check the Id of the current thread

var myThreadId = Thread.CurrentThread.ManagedThreadId;

I create a SynchronizationContext and set is as current

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

Then I send some action to the context (We are now on another thread)

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

Inside this action, I check the ThreadId again

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

This fails. Am I not supposed to be on my original thread again?

like image 344
Stijn Van Antwerpen Avatar asked Dec 09 '22 01:12

Stijn Van Antwerpen


2 Answers

If you create a new SynchronizationContext, it will always wrap the Thread Pool and never execute Send or Post on the UI thread.

From MSDN;

The SynchronizationContext class is a base class that provides a free-threaded context with no synchronization.

For example;

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.
     }         
}

Further Reading

Parallel Computing - It's All About the SynchronizationContext

like image 196
Gusdor Avatar answered Jan 04 '23 22:01

Gusdor


Creating a new SynchronizationContext and using Send or Post is exactly the same as a synchronous delegate invocation as if you'd do it yourself. The code is rather simple (taken from the source):

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

You're trying to mimic the operation of custom contexts, such as the DispatcherSynchronizationContext for example, which is aware of the WPF's UI message loop thread. That behavior does not happen here.

If you're coming from the UI thread, you'll need to capture the context and pass it along.

You can see this more clearly inside the DispatcherSynchronizationContext which queues work to the UI using the Dispatcher class:

/// <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);
    }
}
like image 34
Yuval Itzchakov Avatar answered Jan 04 '23 22:01

Yuval Itzchakov