Issue:
We are trying to use NetHttpBinding and expect to use the web sockets, along with callback implementation.
Now web sockets are by default available if we have a callback contract implemented (when using NetHttpBinding), and it also can be forced on a request/response channel.
Synchronous and Asynchronous call to the service via console app works fine via web sockets (default behavior when calling service with callback contract).
But the moment I try to use Win Form / WPF app, my client application gets in hang state and never gets the callback.
Please note that the everything works as expected when using the wsDualHttpBinding.
While using NetHttpBinding, when I create a WCF service with callback contract implemented, my client does get the web Socket end point, when I add a service reference.
Stack Trace:
<ExceptionType>System.TimeoutException, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</ExceptionType>
<Message>WaitForMessage timed out after 00:00:09.9829894. The time allotted to this operation may have been a portion of a longer timeout.</Message>
<StackTrace>
at System.Runtime.Diagnostics.EtwDiagnosticTrace.WriteExceptionToTraceString(XmlTextWriter xml, Exception exception, Int32 remainingLength, Int32 remainingAllowedRecursionDepth)
at System.Runtime.Diagnostics.EtwDiagnosticTrace.ExceptionToTraceString(Exception exception, Int32 maxTraceStringLength)
at System.Runtime.Diagnostics.EtwDiagnosticTrace.GetSerializedPayload(Object source, TraceRecord traceRecord, Exception exception, Boolean getServiceReference)
at System.Runtime.TraceCore.ThrowingException(EtwDiagnosticTrace trace, String param0, String param1, Exception exception)
at System.Runtime.ExceptionTrace.TraceException[TException](TException exception, String eventSource)
at System.Runtime.ExceptionTrace.AsError(Exception exception)
at System.ServiceModel.Channels.WebSocketTransportDuplexSessionChannel.WebSocketMessageSource.Receive(TimeSpan timeout)
at System.ServiceModel.Channels.SynchronizedMessageSource.Receive(TimeSpan timeout)
at System.ServiceModel.Channels.TransportDuplexSessionChannel.Receive(TimeSpan timeout)
at System.ServiceModel.Channels.TransportDuplexSessionChannel.TryReceive(TimeSpan timeout, Message& message)
at System.ServiceModel.Dispatcher.DuplexChannelBinder.Request(Message message, TimeSpan timeout)
at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan timeout)
at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs)
at System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage methodCall, ProxyOperationRuntime operation)
at System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message)
at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)
at TestService.IService.GetString()
More details:
It happens because WCF channel on client side tries to use the default UI Synchronization Context..
Reference :
http://www.codeproject.com/Articles/17704/WCF-Duplex-Operations-and-UI-Threads
When the service operation is invoked from the UI thread, it will block until the return value has been received. However, the service operation invokes a callback to the client prior to the sending the return value. The callback operation will be marshaled to the UI thread.
Since the UI thread is still waiting for the return value, a deadlock will occur. The root of this problem has to do with synchronization contexts.
By default, WCF provides thread affinity to the current synchronization context that is used to establish a connection with a service.
All service requests and replies will be executed on the thread of the originating synchronization context. In some situations, this may be desired behavior.
At other times, it may not. In our case, it is causing a problem.
So as per this article, if we set the UseSynchronizationContext to false, Call initiated from client app should not go in deadlock..
Article suggests to use following callbackBehavior - [CallbackBehavior(UseSynchronizationContext = false)]
Which does allow my client to handle the callback even from Synchronous call…
However it only works for WsDualHttpBinding .. I want to use it for NetHttp binding and it does not work there…
SynchronizationContext:
https://msdn.microsoft.com/magazine/gg598924.aspx
Reason and workaround:
Problem:
The problem being exposed is in the IChannel.Open path … the issue is if we open in the GUI thread, things fail were as if we open in another thread things work.
Summary:
We identified a potential bug in existing design (way we handle capturing the current synchronizationContext) and confirmed that client.Open() is not working as expected.
Workaround:
The way the client opens the channel needs to be changed.
1. Being/End methods
Try opening the client explicitly after you create it using the following code:
DuplexChannelFactory<IDuplexService> cf = new DuplexChannelFactory<IDuplexService>(ix, "wsDualHttpDuplexServiceEndpoint");
_duplexService = cf.CreateChannel(ix);
((IChannel)_duplexService).EndOpen(((IChannel)_duplexService).BeginOpen(null, null))
The same applies to the non-duplex client!
2. Try calling IChannel.Open explicitly using a different/background thread.
ManualResetEvent mre = new ManualResetEvent(false);
new Thread((s) => { ((IChannel)_duplexService).Open(); mre.Set(); }).Start();
mre.WaitOne();
Explanation
========
Now, as for the reason we see calls alternating … that’s because when we call the methods in a background thread, we’re implicitly opening the channel in a background thread, thus the open succeeds,
whereas when we call the service method on the GUI thread, we’re implicitly opening the channel on the GUI thread which causes the deadlock.
I hope this helps WCF DEV to use this callback feature carefully when working with NetHttpBinding.