WPF +任务 + WCF = 没有同步上下文?

发布于 2024-10-18 05:30:15 字数 3299 浏览 0 评论 0原文

我有一个 WPF 应用程序,它使用 System.Threading.Tasks 在后台调用 WCF 服务。我使用 Task.ContinueWith 将服务调用的结果返回到 WPF UI 线程。我的问题是,虽然延续确实在 UI 线程上运行,但当它运行时,SynchronizationContext.Current 为 null。我可以运行相同的代码,注释掉初始任务中的 WCF 调用,并在 UI 线程上继续,并按预期使用 DispatcherSynchronizationContext。

WCF代理是使用ChannelFactory生成的,并使用wsHttpBinding。没有回调合约。相关代码如下所示:

    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 }

如果我按原样运行此代码,则会在正确的线程上调用 ServiceContinuation(使用 ManagedThreadID 进行验证),但 SynchronizationContext.Current 为 null。如果我注释掉进行服务调用的单行 (result = service.TheServiceMethod();),则可以使用 DispatcherSynchronizationContext 正确调用 ServiceContinuation。

需要注意的是 - SynchronizationContext 不会永久丢失 - 如果我再次单击按钮,按钮单击处理程序确实具有正确的 SynchronizationContext。

我已经捕获了这两种情况的堆栈跟踪;他们有一些区别。我省略了所有相同的位,只包含了它们不同的堆栈顶部,以及一些参考框架:

失败 - 调用 WCF 服务

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

成功 - 否调用 WCF 服务

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

有谁知道为什么,当唯一的区别是 WCF 客户端服务调用(没有回调契约)时,在一种情况下,主线程上的延续将有一个 SynchronizationContext,而在另一种情况下,它不会吗?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文