对WCF客户端的异步调用会阻止后续同步调用

时间:2016-06-14 17:43:30

标签: c# wcf asynchronous async-await deadlock

我在客户端上调用生成的Async方法时遇到WCF问题...如果我等待异步方法,然后在同一客户端上调用非异步方法,则阻塞方法永远不会返回

这是一个微不足道的问题再现:

[ServiceContract]
public interface IService1
{
    [OperationContract] void DoWork();
}

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession, ConcurrencyMode = ConcurrencyMode.Multiple)]
public class Service1 : IService1
{
    public void DoWork()
    {
    }
}

这里没什么复杂的。一个简单的WCF服务(同步实现),公开单个方法。

class Program
{
    static void Main(string[] args)
    {
        var svc = new ServiceHost(typeof(Service1));
        svc.Open();
        RunClient().Wait();    // This is a console app. There is no SynchronizationContext to confuse things.
        svc.Close();
    }

    static async Task RunClient()
    {
        var client = new ServiceReference1.Service1Client();
        client.DoWork();
        Console.WriteLine("Work Done");

        await client.DoWorkAsync();
        Console.WriteLine("Async Work Done");

        Console.WriteLine("About to block until operation timeout...");
        client.DoWork();
        Console.WriteLine("You'll never get here.");

    }
}

请注意,这不是阻止消息抽取线程或忘记拨打ConfigureAwait(false)的人的常见情况。这是一个控制台应用,添加ConfigureAwait对行为没有影响。

奇怪的是,有什么帮助:

await Task.Delay(1);   // Magical healing worker thread context switch

再次调用同步WCF方法之前。因此,看起来WCF在从异步方法调用恢复之后会以某种方式留下一些线程本地上下文,然后将其清除。

知道可能导致这种情况的原因是什么? 来自死锁/非死锁情况的调用堆栈揭示了WCF客户端操作中的一些差异:

好筹码:

System.ServiceModel.dll!System.ServiceModel.Channels.TransportDuplexSessionChannel.Receive(System.TimeSpan timeout)
System.ServiceModel.dll!System.ServiceModel.Channels.TransportDuplexSessionChannel.TryReceive(System.TimeSpan timeout, out System.ServiceModel.Channels.Message message)    System.ServiceModel.dll!System.ServiceModel.Dispatcher.DuplexChannelBinder.Request(System.ServiceModel.Channels.Message message, System.TimeSpan timeout)
System.ServiceModel.dll!System.ServiceModel.Channels.ServiceChannel.Call(string action, bool oneway, System.ServiceModel.Dispatcher.ProxyOperationRuntime operation, object[] ins, object[] outs, System.TimeSpan timeout)
System.ServiceModel.dll!System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(System.Runtime.Remoting.Messaging.IMethodCallMessage methodCall, System.ServiceModel.Dispatcher.ProxyOperationRuntime operation)
System.ServiceModel.dll!System.ServiceModel.Channels.ServiceChannelProxy.Invoke(System.Runtime.Remoting.Messaging.IMessage message)
mscorlib.dll!System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(ref System.Runtime.Remoting.Proxies.MessageData msgData, int type)

坏堆栈:

mscorlib.dll!System.Threading.WaitHandle.InternalWaitOne(System.Runtime.InteropServices.SafeHandle waitableSafeHandle, long millisecondsTimeout, bool hasThreadAffinity, bool exitContext)
mscorlib.dll!System.Threading.WaitHandle.WaitOne(System.TimeSpan timeout, bool exitContext)
System.ServiceModel.Internals.dll!System.Runtime.TimeoutHelper.WaitOne(System.Threading.WaitHandle waitHandle, System.TimeSpan timeout)
System.ServiceModel.dll!System.ServiceModel.Dispatcher.DuplexChannelBinder.SyncDuplexRequest.WaitForReply(System.TimeSpan timeout)
System.ServiceModel.dll!System.ServiceModel.Channels.ServiceChannel.Call(string action, bool oneway, System.ServiceModel.Dispatcher.ProxyOperationRuntime operation, object[] ins, object[] outs, System.TimeSpan timeout)
System.ServiceModel.dll!System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(System.Runtime.Remoting.Messaging.IMethodCallMessage methodCall, System.ServiceModel.Dispatcher.ProxyOperationRuntime operation)
System.ServiceModel.dll!System.ServiceModel.Channels.ServiceChannelProxy.Invoke(System.Runtime.Remoting.Messaging.IMessage message)
mscorlib.dll!System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(ref System.Runtime.Remoting.Proxies.MessageData msgData, int type)

似乎DuplexChannelBinder WCF中的某个位置假定在异步调用完成后,另一个线程正在读取来自通道的消息。

有什么想法?显然,我对将Task.Delay语句添加到我的代码中的想法并不感到激动......

干杯, 标记

1 个答案:

答案 0 :(得分:1)

当使用相同的客户端调用多个操作时,我遇到了类似的问题。 使用相同的对象不能同时调用多个方法,它看起来像是在wcf方法调用中实现的一些锁定。

所以直到1次调用未完成,你才能再次使用同一个对象调用wcf服务。