2

I am using SynchronizationContext as a means to synchronize to the GUI thread, for WinForms and WPF. Recently I ran into an issue with the old style async callbacks:

   private void Button_Click(object sender, RoutedEventArgs e)
    {
        uiContext = SynchronizationContext.Current;

        var cl = new TcpClient();
        cl.BeginConnect("127.0.0.1", 22222, ConnectedCallback, null);
    }
    public void ConnectedCallback(IAsyncResult result)
    {
        if (SynchronizationContext.Current != uiContext)
            uiContext.Post(x => MyUIOperation(), null);
        else
            MyUIOperation();
    }

    public void MyUIOperation()
    {
        Title = "Connected";
    }

    private SynchronizationContext uiContext;

This will throw an exception, because the SynchronizationContext.Current in the callback function is equal to the captured one, and therefore the UI operation is executed in the worker thread of the callback.

Using this exact same code in WinForms works as I had expected.

For now as a workaround, I am capturing the current ManagedThreadId instead and compare it in the callback. What is correct way to handle this?

Update:

I should add that I am modifying a very old existing class that currently uses the following construct:

if (control.InvokeRequired())
    control.BeginInvoke(SomeFunction);
else
    SomeFunction();

I am trying to remove the WinForms dependency, without having much impact on the clients of this class. The SomeFunction() is raising events, so if I just call uiContext.Send() or uiContext.Post() , the order of execution is changed since Post() will always queue the call, and Send() will always block.

Also, this is just a very small piece of code to show the root of my issue. In reality the function doing the Post() can be called from the main thread.

This is targeting .NET 4.0

Night94
  • 965
  • 8
  • 11
  • I would say creating a service which ensures and gives you the UI sync context is the way to go. Then always inject that service to any code part you need and call the Post on Context without any checks everywhere where you need to be sure it is executed on foreground. – VidasV Dec 28 '15 at 15:56
  • Can you post the exact message and stack trace of the exception you are getting? – Leandro Dec 28 '15 at 16:18
  • "The calling thread cannot access this object because a different thread owns it." – Night94 Dec 28 '15 at 16:20
  • 1
    at System.Windows.Threading.Dispatcher.VerifyAccess() at System.Windows.Window.set_Title(String value) at WpfApplication2.MainWindow.MyUIOperation() – Night94 Dec 28 '15 at 16:21
  • 2
    Why do you need to check `if (SynchronizationContext.Current != uiContext)`? Why not to always invoke `Post`? – Yacoub Massad Dec 28 '15 at 16:22
  • Because in my case, the call to `MyUIOperation()` function would need to be called immediately if the `ConnectedCallback` function is called from the main thread. If I use `Post()` that does not happen, the call is always deferred. This is just a very trimmed down example to illustrate that in the case of `ConnectedCallback`, the `SynchronizationContext.Current` is the same as it was in the main thread. So simulating `Control.InvokeRequired` does not seem to work in WPF, the way I do it in the code. But it does in WinForms. – Night94 Dec 28 '15 at 16:34
  • 1
    The code you posted should work. – Ivan Stoev Dec 28 '15 at 17:17
  • 2
    ConnectedCallback is never called on the UI thread. I think you are solving a non-existing problem. Also, you should be using await instead. The APM pattern is obsolete. – usr Dec 28 '15 at 17:21
  • Sorry, I updated my post to be more clear. My main issue is really that I was always under the impression that when you capture the SynchronizationContext in the main thread, it will always be different from the SynchronizationContext.Current of other threads. This does not seem to be the case, so it can't be used to replace a "InvokeRequired" without changing the behavior of the class. – Night94 Dec 28 '15 at 17:48
  • @Night94: There should not be a UI SyncCtx on a background thread, unless that background thread does something it shouldn't do like create a UI object. Can you post a minimal reproduction of the problem? – Stephen Cleary Dec 28 '15 at 20:08
  • @StephenCleary I can attest that `SynchronizationContext.Current` from inside the callback is the same reference as `uiContext`. Tried it on Win8.1 x64, targeting .NET 4.0. https://drive.google.com/file/d/0B91vNcnuPv_GNE10Z3doOEtvSms/view?usp=sharing – Leandro Dec 28 '15 at 20:45

2 Answers2

3

It turned out, in .NET 4.5 the SynchronizationContext is in fact different in the callback function and the if statement would evaluate to true. This was a deliberate change, as discussed here

   WPF 4.0 had a performance optimization where it would
     frequently reuse the same instance of the
     DispatcherSynchronizationContext when preparing the
     ExecutionContext for invoking a DispatcherOperation.  This
     had observable impacts on behavior.
     1) Some task-parallel implementations check the reference
         equality of the SynchronizationContext to determine if the
         completion can be inlined - a significant performance win.
     2) But, the ExecutionContext would flow the
         SynchronizationContext which could result in the same
         instance of the DispatcherSynchronizationContext being the
         current SynchronizationContext on two different threads.
         The continuations would then be inlined, resulting in code
         running on the wrong thread.
Community
  • 1
  • 1
Night94
  • 965
  • 8
  • 11
2

Because in my case, the call to MyUIOperation() function would need to be called immediately if the ConnectedCallback function is called from the main thread.

That means the call to MyUIOperation() would be a blocking call if the ConnectedCallback is invoked in the UI thread, as opposed to non-blocking if it is invoked from the another thread. This non-determinism could cause other problems down the road.

Just call Send instead. According to this article, the call to Send would just invoke the delegate directly if already in the UI thread.

Also, you could just do Dispatcher.Invoke() instead.

Leandro
  • 1,560
  • 17
  • 35
  • See my update. I understand that I can use either Send or Post for synchronization and I agree that the original design was bad to begin with. However in order to not cause a chain reaction of other bad things the logic needs to be "If I am running in the UI thread, call this function immediately, otherwise queue the call", and I would like to use SynchronizationContext and nothing UI framework specific – Night94 Dec 28 '15 at 17:54
  • I understand. In that case, I don't see any good solution other than the workaround that you are already using. I'm not sure whether `SynchronizationContext.Current` is supposed to be different for each thread. The [documentation](https://msdn.microsoft.com/en-us/library/system.threading.synchronizationcontext.current.aspx) says "this is useful for propagating a synchronization context from one thread to another", so I'd say it is shared between threads. You could use `Thread.CurrentThread.IsBackground` instead of `SynchronizationContext.Current != uiContext`, but I wouldn't rely on it either. – Leandro Dec 28 '15 at 18:33