Search code examples
wpfwinformsasynchronoussynchronizationcontext

SynchronizationContext.Current in async callback


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


Solution

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