Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Comparing SynchronizationContext

How do I compare SynchronizationContext? It seems that the same Dispatcher can create different SynchronizationContext when using BeginInvoke. When I drill down into the two (unequal) contexts, I see that the dispatcher Thread ID is the same, yet they are not Equal to each other.

public partial class MainWindow : Window
{
    private SynchronizationContext contexta;
    private SynchronizationContext contextb;
    private SynchronizationContext contextc;
    private SynchronizationContext contextd;

    public MainWindow()
    {
        InitializeComponent();

        contexta = SynchronizationContext.Current;

        Loaded += MainWindow_Loaded;
    }

    private void MainWindow_Loaded(object sender, RoutedEventArgs e)
    {
        contextb = SynchronizationContext.Current;

        Dispatcher.Invoke(() =>
            {
                contextc = SynchronizationContext.Current;
            });

        Dispatcher.BeginInvoke(new Action(() =>
            {
                contextd = SynchronizationContext.Current;
            }));

        Debug.Assert(contexta != contextb);
        Debug.Assert(contexta == contextc);         // fails... why?!?!?
        Debug.Assert(contexta == contextd);         // fails... why?!?!?
        Debug.Assert(contextc == contextd);         // fails... why?!?!?
    }        

Maybe the two of them cannot be used together. I noticed that this actually works:

        contexta.Send(new SendOrPostCallback((s) =>
            {
                contexte = SynchronizationContext.Current;
            }), null);

Update But strangely, it doesn't always work.

    public override void AddRange(IEnumerable<T> items)
    {
        if (SynchronizationContext.Current == _context)
        {
            base.AddRange(items);
        }
        else
        {
            _context.Send(new SendOrPostCallback((state) =>
                {
                    AddRange(state as IEnumerable<T>);
                }), items);
        }
    }

never gets a matched _context and goes on forever, for example. Even though it shouldn't. This latter example the threads actually end up being the same, and there is a context, but it is different.

Update2 Ok, I got it to work, but I really feel uncomfortable about it. Apparently, when you Post or Send, your task is run from the right thread, but if you aren't coming from the UI, it seems that a new SynchronizationContext is generated.

    public override void AddRange(IEnumerable<T> items)
    {
        if (SynchronizationContext.Current == _context)
        {                       
            base.AddRange(items);
        }
        else
        {
            _context.Post(new SendOrPostCallback((state) =>
                {
                    if (SynchronizationContext.Current != _context)
                        SynchronizationContext.SetSynchronizationContext(_context);     // called every time.. strange
                    AddRange(items);
                }), null);
        }
    }

And look at this:

enter image description here

"Requires full trust for the immediate caller. This member cannot be used by partially trusted or transparent code." :(

like image 662
tofutim Avatar asked Nov 21 '12 18:11

tofutim


1 Answers

I think you are interested in BaseCompatibilityPreferences.ReuseDispatcherSynchronizationContextInstance.

This setting dictates whether a single SynchronizationContext instance is used for a given Dispatcher object or not. It is true by default until .net 4, and false in .net 4.5 (this is the behavior change that LukeN observes).

Now if your goal is just to make a direct call rather than calling .Send() I'd say:

  1. when calling .Send(), the DispatcherSynchronizationContext actually just makes a direct call if you are on the correct thread (doesn't use the dispatcher Queue) so you're not gaining much anyway (a few checks and calls from the extra layers of indirection).

  2. if you only code against WPF, you can use Dispatcher.CheckAccess() and Dispatcher.Invoke() to do what you want.

In the general case, there is no way to "compare" two SynchronizationContexts, so you should just call .Send(). It's not likely to be a performance issue anyway, remember that premature optimization is the root of all evil -> measure first.

like image 175
jods Avatar answered Sep 27 '22 18:09

jods