【问题标题】:WCF Asynchronous 'End' method not called after upgrading .NET升级 .NET 后未调用 WCF 异步“结束”方法
【发布时间】:2017-11-01 14:12:28
【问题描述】:

我们有一个用 .NET 3.5 编写的应用程序(控制台应用程序“服务器”自托管一堆 WCF 服务和一些 WPF 客户端)。我想尝试将“服务器”应用程序升级到 .NET 4.6。为了测试,我只是要更改运行时并在 4.6 中添加一些子项目,而将其余项目留在 3.5 中。在顶级项目中,我将目标更改为 4.6,并确保 app.config 文件中包含以下内容:

<startup useLegacyV2RuntimeActivationPolicy="true">
  <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6"/>
</startup>

WCF 服务和其他支持项目在解决方案中,我没有对其进行任何修改。

我也没有修改 WPF 客户端。

在我们的一些服务中,我们在服务器上实现异步开始/结束模式。这对我来说是新的,因为我学习了异步/等待模式的 WCF(尽管我通常熟悉开始/结束)。客户端上的任何异步需求都留给调用代码。

服务

public interface IMyServiceCallback
{
    void UnrelatedNotifyClientsMethod(Notification message);
}



[ServiceContract(CallbackContract = typeof(IMyServiceCallback))]
public interface IMyService
{
    // ...

    [OperationContract(AsyncPattern = true)]
    IAsyncResult BeginFetchSomething(int howMany, AsyncCallback callback, object state);
    FetchSomethingResult EndFetchSomething(IAsyncResult result);

    // ...
}



[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple)]
public class MyService : IMyService
{
    // ...

    [PermissionSetAttribute(SecurityAction.LinkDemand, Name = "FullTrust")]
    public IAsyncResult BeginFetchSomething(int howMany, AsyncCallback callback, object state)
    {
        AsyncResult<FetchSomethingResult> something = new AsyncResult<FetchSomethingResult>(callback, state);

        BackgroundWorker backgroundWorker = new BackgroundWorker();
        backgroundWorker.DoWork += new DoWorkEventHandler((sender, args) =>
        {
            try
            {
                FetchSomethingResult resultData = Database.FetchSomethingQuery(howMany);
                something.Result = resultData;
                something.Complete(true);
            }
            catch (Exception e)
            {
                Log(e);
                something.HandleException(e, false);
            }
            backgroundWorker.Dispose();
        });

        backgroundWorker.RunWorkerAsync();
        return something;
    }


    public FetchSomethingResult EndFetchSomething(IAsyncResult result)
    {
        AsyncResult<FetchSomethingResult> something = result as AsyncResult<FetchSomethingResult>;
        something.AsyncWaitHandle.WaitOne();
        return something.Result;
    }

    // ...

    // other similar methods

    // ...

}



public class AsyncResult : IAsyncResult
{

    // ...

    public void Complete(bool completedSynchronously)
    {
        lock (this.Mutex)
        {
            this.IsCompleted = true;
            this.CompletedSynchronously = completedSynchronously;
        }
        this.SignalCompletion();
    }

    protected void SignalCompletion()
    {
        (this.AsyncWaitHandle as ManualResetEvent).Set();
        ThreadPool.QueueUserWorkItem(d => { this.InvokeCallback(); });
    }

    protected void InvokeCallback()
    {
        if (this.Callback != null)
        {
            this.Callback(this);
        }
    }

    public void HandleException(Exception e, bool completedSynchronously)
    {
        lock (this.Mutex)
        {
            this.IsCompleted = true;
            this.CompletedSynchronously = completedSynchronously;
            this.Exception = e;
        }
        this.SignalCompletion();
    }

    // ...
}

客户端(由 Visual Studio“添加服务引用”生成)

[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")]
public partial class MyServiceClient : System.ServiceModel.DuplexClientBase<NameSpace.ServiceClients.IMyServiceClient>, NameSpace.ServiceClients.IMyServiceCliene
{
    // ...

    public NameSpace.ServiceClients.FetchSomethingResult FetchSomething(int howMany)
    {
        return base.Channel.FetchSomething(howMany);
    }

    // ...

}

在客户端,我们有一些通用类来包装和公开我们的服务,调用可以发生在主线程上,但通常是通过后台工作人员在后台线程上进行的。通过简单地将服务器应用程序从 .NET 3.5 升级到 4+ 并且不进行任何更改,服务器上的 End 方法不再被调用。我已经确认 Begin 方法返回并且工作人员调用 .Complete() 并调用回调,但之后没有任何反应。 1分钟后,客户端会在调用时抛出超时异常。

我们的代码库相当大且复杂,阅读 .NET 4 迁移说明后,我没想到会有任何行为变化。

编辑:我附上了微软服务跟踪实用程序的屏幕截图,显示了在更新之前(顶部,3.5)和之后(底部,4.6)对 FetchProductVersion 的相同调用。

编辑 2/3:在某些情况下失败似乎不一致。

【问题讨论】:

  • 这可能很明显,但您确认您没有遇到跨线程所有权问题吗?在 3.5 中,这会导致警告,并且在 .net 4+ 中,事情会继续工作(或者至少我记得我的 winform 日子),在最好的情况下这是一个例外,在最坏的情况下会像这样死去。您是否尝试过明确获取 IAsyncResult 的等待句柄然后获取结果?如果可行,那么您很可能会遇到我想像的跨线程问题。由于这是 WPF,调度程序不能很好地处理后台线程;请务必尝试在调度程序上手动调用以查看它是否有帮助。
  • 此外,虽然这不是 wpf 特定的 - 我们确实在WPF Chat channel 中有一些知识渊博的人:来加入我们的讨论,因为 cmets 的位置相当有限。您可能还可以在 C# 聊天频道中对此进行讨论。如果没有 MCVE,这很难回答,而且很可能会浪费赏金并可能被搁置(因为这归类为需要 MCVE 的调试帮助问题)
  • @Maverik 在看到来自跟踪实用程序的图表后,我怀疑存在跨线程问题,我很可能会在今天尝试对此进行调查。我暗暗希望这是升级的一些已知问题——但现在我发现这可能很难回答。我也会在聊天中摇摆不定,谢谢你的提示。

标签: c# .net wcf upgrade


【解决方案1】:

您必须将IAsyncResult.CompletedSynchronously 属性正确设置为false,因为您以异步方式完成IAsyncResult。与 .NET Framework 4.6 中的 WCF 不同,.NET Framework 3.5 中的 WCF 在这种情况下只是忽略它。

所以将something.Complete(true); 更改为something.Complete(completedSynchronously: false);。

您可以通过在其 getter 中放置断点来检查谁和何时使用 IAsyncResult.CompletedSynchronously:

bool _completedSynchronously = false;
public bool CompletedSynchronously
{
    get
    {
        // Set breakpoint here.
        return _completedSynchronously;
    }
    set
    {
        _completedSynchronously = value;
    }
}

如果您使用 .NET Framework 3.5 启动 WCF 服务,您会注意到它实际上从未被调用过。所以旧版本的 WCF 只是忽略它。如果您使用 .NET Framework 4.6 启动服务,您将看到 WCF 内部类 DispatchOperationRuntime 大量使用它。特别是 InvokeBegin() 和 InvokeCallback() 函数。

【讨论】:

  • 做到了!我能够通过测试我们的应用程序来确认 - 我在 CompletedSynchronously 的 getter 上添加了断点,并确认两个运行时版本之间的访问是您所预测的。我不确定为什么我们一开始就让它成为现实,但我很乐意改变它。非常感谢。
【解决方案2】:

我发现在深入挖掘之前你应该验证两个低悬的项目。

  1. 验证异步开始和结束方法具有相同的后缀名称。

WCF 自动将入站消息路由到 Begin 方法,并将 End 调用的结果路由到出站消息。

在您的伪代码中,它们不遵循上述命名约定:

IAsyncResult BeginFetchSomething(int howMany, AsyncCallback callback, object state);
FetchSomethingResult EndFetchJobResults(IAsyncResult result);
  1. 验证您正在适当地装饰您的方法。 MSDN 提供了以下示例:

    [OperationContract (AsyncPattern=true)]
    IAsyncResult BeginAdd(int a, int b, AsyncCallback cb, AsyncState s);//async begin
    
    [OperationContract]
    int EndAdd(IAsyncResult r);//async end
    
    [OperationContract]
    int Add(int a, int b);//synchronous complete action
    

在您的伪代码中缺少 OperationContract 属性。希望就是这么简单。祝你好运!

MSDN Source

【讨论】:

  • 道歉 - 不匹配是我在将我们的代码缩减为伪代码时的错误。此外,this 建议 OperationContract 属性只需要在 Begin 方法上——这是我们之前在 3.5 中使用的方法——这不正确吗?
  • 在我兴奋中,我忘了包括我的来源。我已将其添加到帖子中。我没有启动工作服务,所以我不确定不需要的属性。在我自己尝试之前,我对一切都持怀疑态度。
猜你喜欢
  • 1970-01-01
  • 2014-01-27
  • 1970-01-01
  • 2011-09-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-03-12
相关资源
最近更新 更多