WPF + Tasks + WCF = No SynchronizationContext?

2.5k views Asked by At

I have a WPF application that is using System.Threading.Tasks to call a WCF service in the background. I'm using Task.ContinueWith to return the results of the service call to the WPF UI thread. My issue is that, although the continuation does run on the UI thread, when it does SynchronizationContext.Current is null. I can run the same code, commenting out the WCF call in the initial Task, and the continuation is on the UI thread, with a DispatcherSynchronizationContext as expected.

The WCF proxy is generated using ChannelFactory, and uses wsHttpBinding. There is no callback contract. The relevant code is shown below:

    private TaskScheduler _uiScheduler;

    public MainWindow()
    {
        InitializeComponent();
        _uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        var serviceTask = new Task<Int32>(ServiceCallWrapper, 
            CancellationToken.None, 
            TaskCreationOptions.None);

        var continueTask = serviceTask.ContinueWith(result => ServiceContinuation(result.Result),
                                                    CancellationToken.None,
                                                    TaskContinuationOptions.OnlyOnRanToCompletion, 
                                                    _uiScheduler);

        serviceTask.Start();
    }

    private Int32 ServiceCallWrapper()
    {
        Int32 result = 0;

        var service = {elided - initializes service using ChannelFactory };
        result = service.TheServiceMethod();
        service.Close();

        return result;
    }

    private void ServiceContinuation(Int32 result)
    { elided }

If I run this code as is, the ServiceContinuation is called on the correct thread (verified using ManagedThreadID), but SynchronizationContext.Current is null. If I comment out the single line that makes the service call (result = service.TheServiceMethod();), then ServiceContinuation is correctly called with a DispatcherSynchronizationContext.

One note - the SynchronizationContext is not permanently lost - if I Click on the button again, the button click handler does have the correct SynchronizationContext.

I've captured stack traces for the two cases; they have a few differences. I've left out all of the bits that are identical, and only included the top of the stacks where they differ, plus a few frames for reference:

Fails - Calls WCF Service

WpfContinuationsTest.MainWindow.ServiceContinuation
WpfContinuationsTest.MainWindow.<Button_Click>b__0
System.Threading.Tasks.Task`1+<>c__DisplayClass17.<ContinueWith>b__16
System.Threading.Tasks.Task.InnerInvoke
System.Threading.Tasks.Task.Execute
System.Threading.Tasks.Task.ExecutionContextCallback
System.Threading.ExecutionContext.runTryCode
System.Runtime.CompilerServices.RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup
System.Threading.ExecutionContext.RunInternal
System.Threading.ExecutionContext.Run
System.Threading.Tasks.Task.ExecuteWithThreadLocal
System.Threading.Tasks.Task.ExecuteEntry
System.Threading.Tasks.SynchronizationContextTaskScheduler.PostCallback

Succeeds - No Call To WCF Service

WpfContinuationsTest.MainWindow.ServiceContinuation
WpfContinuationsTest.MainWindow.<Button_Click>b__0
System.Threading.Tasks.Task`1+<>c__DisplayClass17.<ContinueWith>b__16
System.Threading.Tasks.Task.InnerInvoke
System.Threading.Tasks.Task.Execute
System.Threading.Tasks.Task.ExecutionContextCallback
System.Threading.ExecutionContext.Run
System.Threading.Tasks.Task.ExecuteWithThreadLocal
System.Threading.Tasks.Task.ExecuteEntry
System.Threading.Tasks.SynchronizationContextTaskScheduler.PostCallback

Does anyone know why, when the only difference is a WCF client service call (with no callback contract), in one case the continuation on the main thread would have a SynchronizationContext, and in the other case it wouldn't?

1

There are 1 answers

2
Travis H On