SynchronizationContext Send() supposed to be the same thread?
Asked Answered
H

2

5

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?

Herzel answered 26/8, 2015 at 10:34 Comment(4)
Maybe read Parallel Computing - It's All About the SynchronizationContext by Stephen Cleary.Braithwaite
"I try to handle an event on the same thread as it was created". Can you clarify what you mean by "creating an event"?Gurl
By creating an event I meant attaching the event handler to the delegate.Herzel
CurrentThread and CurrentContext are not the same. You execute your action in another Thread which have another ThreadId.Microphyte
A
2

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);
    }
}
Ashram answered 26/8, 2015 at 10:56 Comment(5)
Yes indeed, I am using a SynchronizationContext but check for behavior that is implemented by the WindowsFormsSynchronizationContext.Herzel
@StijnVanAntwerpen You can't achieve it the way you're doing it.Ashram
True, but actually I don't need to. If there already is a context I use that one. If it happens to be the WindowsFormsSynchronizationContext than it will do as expected, if it is not, then it doesn't really matters.Herzel
" the same as a synchronous delegate invocation" - Can you double check that? I've seen conflicting reports.Fitted
@Fitted The first code snippet i've posted is the actual implementation for SynchronizationContext.Ashram
F
13

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

Fitted answered 26/8, 2015 at 10:46 Comment(1)
I find this also a very good answer. The other answer however explained the different behavior of different implementations.Herzel
A
2

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);
    }
}
Ashram answered 26/8, 2015 at 10:56 Comment(5)
Yes indeed, I am using a SynchronizationContext but check for behavior that is implemented by the WindowsFormsSynchronizationContext.Herzel
@StijnVanAntwerpen You can't achieve it the way you're doing it.Ashram
True, but actually I don't need to. If there already is a context I use that one. If it happens to be the WindowsFormsSynchronizationContext than it will do as expected, if it is not, then it doesn't really matters.Herzel
" the same as a synchronous delegate invocation" - Can you double check that? I've seen conflicting reports.Fitted
@Fitted The first code snippet i've posted is the actual implementation for SynchronizationContext.Ashram

© 2022 - 2024 — McMap. All rights reserved.