11

I'm seeing a problem with WCF when calling the generated Async methods on the client... If I await on an async method, and then subsequently call a non-async method on the same client, the blocking method never returns.

Here's a trivial reproduction of the problem:

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

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

Nothing complicated here. A simple WCF service (implemented synchronously) that exposes a single method.

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.");

    }
}

Please note, this isn't the usual case of someone blocking the message pumping thread or forgetting to call ConfigureAwait(false). This is a console app, and adding ConfigureAwait has no effect on the behavior.

Bizarrely, what does help is to do:

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

before calling the synchronous WCF method again. So, it appears that WCF somehow leaves some thread-local context behind after resuming from an async method call, which is then cleaned up.

Any idea what could be causing this? The call stack from the deadlock/non-deadlock case reveals some differences in the WCF client operation:

Good stack:

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)

Bad stack:

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)

It seems that somewhere in the DuplexChannelBinder WCF assumes there's another thread reading messages from the channel, after an async call has completed.

Any thoughts? Obviously, I'm not thrilled with the idea of adding healing Task.Delay statements to my code...

Cheers, Mark

Mark
  • 1,784
  • 16
  • 26
  • So, I don't think the problem is a thread-local storage context issue. I think it's something to do with the async continuation being called from the context of the WCF stack as a result of IO completion. When the async continuation returns (i.e. when we await something else), some more WCF framework code runs that alters the state of the client channel, and that "fixes" the problem. – Mark Jun 15 '16 at 13:01
  • Any news? found some real solution? I'm having the "same" (?) problem... – ephraim Apr 29 '18 at 12:18
  • Sorry if this is dumb...but is there a guarantee that the WCF library creates a host that has an independent thread of execution? Hosting the client and the host in the same thread seems problematic from the start. Could you shove the host object into a separate thread? – Ritch Melton Jan 17 '19 at 18:40
  • It doesn't matter if the client and server are in separate processes, the same problem persists. The above code is just a minimal reproduction case. – Mark Jan 25 '19 at 18:15

1 Answers1

1

I ran into similar problem when using same client to call multiple operations. using same object can not call multiple method at same time, it looks like some locking implemented inside wcf method call.

so until 1 call not completed you can not make another call to wcf service with same object.