One of the most common problems we debug with WCF proxies invoked from WinForm/XAML app’s is a perceived hang when duplex contract’s are involved. The app usually has a “click to invoke proxy” button that invokes a method (which will callback the client) on the server and the app will freeze waiting for the response. I wanted to talk a little bit more about this today that hopefully will prove useful if anyone is debugging similar issues.
Most often the reason for this behavior is due to a argument in the ServiceBehaviorAttribute/CallbackBehaviorAttribute class called UseSynchronizationContext. This property indicates that the WCF runtime will schedule all calls to the server/callback dispatcher on the SynchronizationContext that was captured when the runtime was built. Refer to these links to know more about SynchronizationContext. SynchronizationContext is used to ensure that a synchronous/async callback is invoked on the same thread. People who have worked with winforms would know about this. Basically to modify any control on winform, the caller must access the the control on the same thread in which the controls were created or else you get this wonderful error message.
An unhandled exception of type ‘System.InvalidOperationException’ occurred in System.Windows.Forms.dll
Additional information: Cross-thread operation not valid: Control ‘button1’ accessed from a thread other than the thread it was created on.
If the callback is invoked on the SynchronizationContext of the thread that created the control this problem can be avoided. WCF allows the user to specify whether the WCF should invoke operations (on server) or callbacks (on clients) on a particular SynchronizationContext. This property defaults to true in both attributes and what this implies is that if WCF detects a SynchronizationContext during the creation of the runtime then it will cache it and will streamline all further invocations on that. By default console programs dont have SynchronizationContext set (i.e SynchronizationContext.Current will be null) and so this is not a problem for those applications. Winforms, on the other hand, will populate a SynchronizationContext object when the Form constructor is called and so when you create a ServiceHost/Proxy/ChannelFactory within the scope of the winform (or in any of the winform event code), its cached by the runtime. I will use this simple duplex contract to explain the problem.
[ServiceContract(CallbackContract = typeof(IDownloadCallbackService))]
public interface IDownloadService
public interface IDownloadCallbackService
The client is a simple winform app that has one button and in the click event of the button, it creates a duplex channel to the service and invokes the Download operation. The server will just invoke the callback channel and call DownloadComplete.
public void Download()
Console.WriteLine(“CLient invoked download”);
Console.WriteLine(“Notified client of download complete”);
//Callback implementation on the client
public void DownloadComplete()
MessageBox.Show(“Server notified that download is complete”);
//Client code to create a duplex channel
private void button1_Click(object sender, EventArgs e)
DuplexChannelFactory<IDownloadService> factory = new DuplexChannelFactory<IDownloadService>(typeof(DownloadCallbackService), new WSDualHttpBinding());
IDownloadService client = factory.CreateChannel(new InstanceContext(new DownloadCallbackService()), new EndpointAddress(“http://localhost:8080/DownloadService”));
So the sequence is Client->Download->DownloadComplete->returns to Download->returns to Client. Since the duplex channel was created in the button click event, the SynchronizationContext of the winform app is captured and when a message for DownloadComplete arrives at the client’s dispatcher it will schedule the processing of the message on that SynchronizationContext. This message will not be processed as the the SynchronizationContext is busy waiting for the Download call to complete and Download is blocked on the server waiting for DownloadComplete and hence the deadlock.
Solution is to set UseSynchronizationContext property to false on the CallbackBehavior attribute.
public class DownloadCallbackService : IDownloadCallbackService
If you see similar problem on server and the server is hosted in a winforms app then set UseSynchronizationContext=false on the ServiceBehaviorAttribute.
One final comment, the UseSynchronizationContext is checked when the DuplexChannelFactory is created. When using the DuplexChannelFactory model to create channels as shown above, ensure that the type passed in the constructor is the type of class on which the CallbackBehavior is defined. For example, if the factory is created as show below, then the runtime will not be able to look up a CallbackBehavior and hence will use the default values.
DuplexChannelFactory<IDownloadService> factory = new DuplexChannelFactory<IDownloadService>(typeof(IDownloadService), new WSDualHttpBinding());