Click here to Skip to main content
15,867,453 members
Articles / Programming Languages / C# 4.0

Synchronous Web Service Calls with Silverlight: Dispelling the async-only myth

Rate me:
Please Sign up or sign in to vote.
4.95/5 (47 votes)
16 Nov 2008LGPL311 min read 422.9K   4K   81   117
In this article, we look at the asynchronous web service model in Silverlight, and how it can be augmented to allow synchronous web service calls. We also explore efficient channel caching, and asynchronous Silverlight Unit Tests.
Image 1

Contents

Introduction

I like to create and explore, and Silverlight offers an interesting opportunity to explore a new environment; one in which some things, like the asynchronous web service model, work a little differently than we are used to when developing for the desktop CLR. So you may have heard that synchronous web service calls are not possible in Silverlight.

No more synchronous web request, Calling Web Services Synchronously, omg Silverlight asynchronous is evil, Consuming services using Silverlight 2, Silverlight 2 Hangs..., Call the Service Asynchronously

Well, in fact, they are. In this article I'll show you how to perform synchronous web service calls, and introduce you to some types that make performing synchronous calls with generated channel proxies a breeze. I'll also provide a quick overview of the Microsoft Silverlight Unit Testing framework.

Some readers may initially protest that synchronous web service calls have no place in a principally GUI application, because it risks blocking the UI thread, leading to an unresponsive UI etc. And while I agree that it's critical not to block the UI thread as to maintain an application's responsiveness, I do believe there is a legitimate case for synchronous web service calls on non-UI threads. You see, after I ported some of my recent projects from 1.1 to Silverlight 2, I found that not only was the asynchronous model difficult and unnecessary in many situations, it also meant having to rethink the architecture in order to make them async-compatible. For complex scenarios, where background processing is employed, the mandatory asynchronous model is inelegant.

In essence, if one is not working on the UI thread, then why not have synchronous service calls at one's disposal? Certainly the desktop CLR allows their use (think WPF XBAP), why not the Silverlight CLR? There is a good synopsis of the for and against arguments here.

It was only after some experimentation that I hit upon the techniques that I am going to share with you in this article.

Silverlight WCF Recap

Until recently, with Silverlight 1.1, synchronous web service calls were indeed supported. Visual Studio generated the synchronous methods in the channel proxies and interfaces. There was one catch however: any web service call could only take place on the UI thread; otherwise an InvalidOperationException would ensue. With Silverlight 2 RTW, we see that the generated proxies no longer contain the methods for consuming services synchronously. Though now we are able to call a web service from any thread.

So what is happening here? Well, Silverlight 2 web services still have thread affinity with the UI thread, it's just now there is some behind the scenes processing taking place. When a web service call is made, it is placed in a queue to be actioned by the UI thread.

Let's look at how web service calls are generally done in Silverlight 2. Firstly, we generate a proxy by creating a service reference. We then subscribe to the [MethodName]Completed event as shown in the following excerpt.

C#
var client = new SimpleServiceClient();
client.GetGreetingCompleted += ((sender, e) => { DisplayGreeting(e.Result); });
client.GetGreetingAsync("SimpleServiceClient");

When the client.GetGreetingAsync call is made, it is queued for the UI thread. Even if the call is made from the UI thread, it is still queued. Once the call completes the GetGreetingCompleted handler is invoked on the same thread that the GetGreetingAsync call was made.

Turning Asynchronous Calls Synchronous

That's all well and good, but what if we are developing a set of class libraries in a disjoint and modular fashion, with contracts that haven't been written with asynchronous execution in mind, and where return values are expected? It's quite a constraint, and one that may force us to violate implementation visibility by requiring consumers to know that they shouldn't expect to receive a return value, because a web service call may occur. Clearly being able to perform synchronous service calls is a useful thing. Their omission was not to prevent browser lockups but was, according to Peter Bromberg, to allow for cross-browser support as Silverlight needs to implement the NPAPI plugin model.

First Assumptions

You may, like the author did, assume that you could simulate a synchronous web service call by simply using e.g. a ManualResetEvent to block until a result is achieved. This incorrect approach is demonstrated in the following excerpt.

(Don't use this code)

C#
var resetEvent = new ManualResetEvent(false);
var client = new SimpleServiceClient();
client.GetGreetingCompleted += ((sender, e) =>			                                	
{
     	DisplayGreeting(e.Result);
        resetEvent.Set();
     	});
client.GetGreetingAsync("SimpleServiceClient");
resetEvent.WaitOne(); /* This will block indefinately. */ 

Unfortunately this doesn't get us anywhere. The reason is that the instigation of the service call must be performed on the UI thread. When we call resetEvent.WaitOne(), we are blocking the UI thread, which means the service call never takes place. Why? In the background, the call to client.GetGreetingAsync is actually placed in a message queue, and will only be actioned when the thread is not executing user code. If we block the thread, the service call never takes place.

To further demonstrate this point, let's take a look at our previous example, but with a slight modification:

C#
var client = new SimpleServiceClient();
client.GetGreetingAsync("SimpleServiceClient");
/* Sleep for a moment to demonstrate that the call doesn't occur until later. */
Thread.Sleep(1000); 
/* Subscribing to the event on the UI thread, after the service call, still works! */
client.GetGreetingCompleted += ((sender, e) => { DisplayGreeting(e.Result); });

Notice that the subscription to the GetGreetingCompleted event occurs after the actual GetGreetingAsync call. The result, however, is the same as before; the late subscription does not prevent the GetGreetingCompleted handler from being called.

What is happening here is that the web service call is queued. Nothing happens until the UI thread has a chance to invoke it. The Begin[Method] web service call returns immediately, but the underlying WCF call doesn't take place until later, as shown in the following diagram.

UI Message Queue
Figure: UI Message Queue

We can see now why blocking the UI thread doesn't work, and it's a good thing that it doesn't.

So, what's the answer? Well, we can't call the service synchronously from the UI thread. But, we can call it in a synchronous manner from a non-UI thread using a ChannelFactory, as the following excerpt shows.

C#
ThreadPool.QueueUserWorkItem(delegate
{
	var channelFactory = new ChannelFactory<ISimpleService>("*");
	var simpleService = channelFactory.CreateChannel();
	var asyncResult = simpleService.BeginGetGreeting("Daniel", null, null);
	string greeting = null;
	try
	{
		greeting = simpleService.EndGetGreeting(asyncResult);
	}
	catch (Exception ex)
	{
		DisplayMessage(string.Format(
    "Unable to communicate with server. {0} {1}", 
			ex.Message, ex.StackTrace));
	}
	DisplayGreeting(greeting);
});

The ChannelFactory creates a dynamic proxy that we can use to wait on the result of our service method. I originally dismissed this familiar pattern when I found it blocked indefinitely on the UI thread. It wasn't until I tried it on a non-UI thread that my eureka moment occurred.

So, we see that care must be taken in order to avoid making the mistake of attempting this technique from the UI thread. If this happens, our Silverlight application will hang. Unfortunately the System.ServiceModel.ChannelFactory doesn't check to see if it is executing on the UI thread, so we need to come up with a nice means for this ourselves.

Enter the SynchronousChannelBroker.

The SynchronousChannelBroker is a class I have written that links all the steps together, and makes performing synchronous web service calls easy and safe.

To assist in adapting dynamic service channels, I have created several BeginAction delegates that look much like this:

public delegate IAsyncResult BeginAction<TActionArgument1>(
TActionArgument1 argument1, AsyncCallback asyncResult, object state);

Much like the generic Funcs, they allow us to consume the service proxy in a reasonably simple way, without having to augment or replace the Visual Studio Add Service Reference functionality.

So why can't we build a 'SynchronousChannelFactory' that will use the method signatures present in the service contract? Well we could, but it would be a quite a task. Visual Studio's code generator produces interfaces that use the aync pattern "Begin[MethodName]", "End[MethodName]", so if we wanted to do that then we might have to throw away a lot of the infrastructure. One might choose to write the service interfaces manually, but the downside might be that we would be introducing yet more code to maintain. In any case, it's a scenario that is outside the scope of this article.

In addition to providing us with the means to perform synchronous web service calls, I have also included a ChannelManager that efficiently caches service channels. The following excerpt shows how to perform a synchronous channel call using a cached service channel.

var simpleService = ChannelManager.Instance.GetChannel<ISimpleService>();
string result = string.Empty;
try
{
	/* Perform synchronous WCF call. */
	result = SynchronousChannelBroker.PerformAction<string, string>(
		simpleService.BeginGetGreeting, simpleService.EndGetGreeting, "there");
}
catch (Exception ex)
{
	DisplayMessage(string.Format("Unable to communicate with server. {0} {1}", 
		ex.Message, ex.StackTrace));
}
DisplayGreeting(result);

Of course it's all strongly typed, and the generic types must match the parameter types in the service interface methods.

In the above example we retrieve the ISimpleService service channel from the ChannelManager. If this is the first time that this service type has been retrieved then a new channel will be created and cached, otherwise a cached channel will be returned. Once the channel is retrieved we use the SynchronousChannelBroker to invoke the Begin and End methods and return the result. The following diagram provides an overview of what is happening internally.

Synchronous Web Service Execution Flowchart
Figure: Flowchart of Synchronous Web Service Call

The SynchronousChannelBroker's PerformAction has various overloads to match those of your service interfaces. In the previous example PerformAction looks like this:

public static TReturn PerformAction<TReturn, TActionArgument1>(
	BeginAction<TActionArgument1> beginAction, 
	EndAction<TReturn> endAction, TActionArgument1 argument1)
{
	EnsureNonUIThread();
	var beginResult = beginAction(argument1, null, null);
	var result = endAction(beginResult);
	return result;
}

We see that in order to prevent the service call from happening on the UI thread, we call EnsureUIThread(). This method throws an exception if, you guessed it, the current thread is the UI thread. To do this it uses the UISynchronizationContext.

The three main classes of interest are shown in the following diagram.

Main classes
Figure: Class Diagram of main project classes.

UISynchronizationContext

The included UISynchronizationContext uses the applications RootVisual (usually a Page) to get the UI thread's Dispatcher. From the Dispatcher we are able to create a DispatcherSynchronizationContext, which allows us to perform synchronous actions on the UI thread as well as asynchronous ones.

Sometimes we might like to use the UISynchronizationContext before the root visual has been assigned. For this we are able to use the various Initialize overloads in order to assign the context with the UI Dispatcher. An example of this can be seen in the Page class constructor, in the SilverlightExamples project.

UISynchronizationContext.Instance.Initialize(Dispatcher);

The UISynchronizationContext leverages a DispatcherSynchronizationContext initialized with the Dispatcher, and allows us to invoke methods on the UI thread synchronously. Note that the Dispatcher only allows us to invoke asynchronously with BeginInvoke.

Managing Channels in an Efficient Manner

As mentioned earlier, the ChannelManager is used to create or retrieve cached channel proxies. When the channel enters a faulted state it is removed from the cache and recreated when it is next requested. By using our own caching mechanism there are a number of benefits. Some of them are listed below:

  • Security negotiation is only performed once.
  • Avoids having to explicitly close a channel on each use.
  • We can add additional initialization functionality.
  • We can fail early if the proxy is unable to communicate with the server.

Caching of the channel is done in the following manner within the ChannelManager.

readonly Dictionary<Type, object> channels = new Dictionary<Type, object>();
readonly object channelsLock = new object();

public TChannel GetChannel<TChannel>()
{
	Type serviceType = typeof(TChannel);
	object service;

	lock (channelsLock)
	{
		if (!channels.TryGetValue(serviceType, out service))
		{
			/* We don't cache the factory as it contains a list of channels 
			 * that aren't removed if a fault occurs. */
			var channelFactory = new ChannelFactory<TChannel>("*");

			service = channelFactory.CreateChannel();
			var communicationObject = (ICommunicationObject)service;
			communicationObject.Faulted += OnChannelFaulted;
			channels.Add(serviceType, service);
			communicationObject.Open(); /* Explicit opening of the channel 
						 * avoids a performance hit.  */
			ConnectIfClientService(service, serviceType);
		}
	}

	return (TChannel)service;
}

When a channel faults we remove it from the cache as shown in the following excerpt:

C#
/// <summary>
/// Called when a channel faults.
/// Removes the channel from the cache so that it is 
/// replaced when it is next required.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="System.EventArgs"/> 
/// instance containing the event data.</param>
void OnChannelFaulted(object sender, EventArgs e)
{
	var communicationObject = (ICommunicationObject)sender;
	communicationObject.Faulted -= OnChannelFaulted;

	lock (channelsLock)
	{
		var keys = from pair in channels
				   where pair.Value == communicationObject
				   select pair.Key;

		/* Remove all items matching the channel. 
		 * This is somewhat defensive as there should only be one instance 
		 * of the channel in the channel dictionary. */
		foreach (var key in keys.ToList())
		{
			channels.Remove(key);
		}
	}
}

Functional Programming with Known Service Contract Method Signatures

If the service channel contains the method signatures BeginInitiateConnection and EndInitiateConnection, they will be invoked automatically when the channel is first created. By initiating the connection when the channel is created we are able to fail early.

Assuming that we will mostly be looking to use generated proxies, I wanted to provide a way to call a method to initiate the service channel when the channel is created. To do this I chose to use reflection to look for a method called InitiateConnection. The generated proxy will of course have BeginInitiateConnection and EndInitiateConnection, and when the channel is created we call these methods as shown in the following excerpt.

C#
/// Attempts to excercise the <code>IServiceContract.InitiateConnection</code> method
/// on the specified service if that service is an <code>IServiceContract</code>.
/// That is, if the service has a method with the signature InitiateConnection(string),
/// it will be invoked in a functional manner.
/// </summary>
/// <param name="service">The service to attempt a connection.</param>
/// <param name="serviceType">Type of the service for logging purposes.</param>
/// <exception cref="TargetInvocationException">Occurs if the service implements <code>IServiceContract</code>, 
/// and the call to ConnectFromClient results in a <code>TargetInvocationException</code></exception>
void ConnectIfClientService(object service, Type serviceType)
{
	var beginMethodInfo = serviceType.GetMethod("BeginInitiateConnection");
	if (beginMethodInfo == null)
	{
		return;
	}

	beginMethodInfo.Invoke(service, new object[] { ChannelIdentifier, new AsyncCallback(ar =>
       	{
			var endMethodInfo = serviceType.GetMethod("EndInitiateConnection");
			if (endMethodInfo == null)
			{
				return;
			}
			try
			{
				var result = (string)endMethodInfo.Invoke(service, new object[] {ar});
				Debug.WriteLine("Connected from client successfully. Result: " + result);
				/* TODO: Do something with the result such as log the it somewhere. */
			}
			catch (InvalidCastException)
			{
				/* TODO: log that web server has invalid ConnectFromClient signature. */
			}
       	}), null });
}

The Microsoft Silverlight Testing Framework

Provided herewith is a brief summary of my experience so far with the Microsoft Silverlight Unit Testing Framework. I have been pleased by the quality of the framework, and the Silverlight developers have eaten their own dog food, as they say, with this one.

We will not go into too much detail on how to use the unit testing framework, but I thought it worth briefly discussing how to set it up and perform asynchronous unit testing with it, since that is directly relevant.

Getting Started with the Microsoft Silverlight Unit Testing Framework

The framework comes in the form of some Visual Studio templates. I must admit that I was a bit lazy installing the templates, and rather chose to create a new Silverlight application, reference the assemblies shown below, and amend the Application_Startup method in the App.xaml.

Silverlight Unit Test Imports Screenshot
Figure: Silverlight Testing Framework imports.

C#
void Application_Startup(object sender, StartupEventArgs e)
{
	this.RootVisual = UnitTestSystem.CreateTestPage();
}

Once this was done, I was away.

Asynchronous Tests

As it turns out, writing unit tests for methods invoked asynchronously is easy with the Silverlight Testing tools. To accomplish asynchronous testing the Microsoft.Silverlight.Testing.AsynchronousAttribute is applied and, on conclusion of the test, the TestComplete method is called. Note that TestComplete is a method of Microsoft.Silverlight.Testing.SilverlightTest, so one must extend this class in order to use it.

C#
[TestMethod]
[Asynchronous]
public void InvokeSynchronouslyShouldPerformActionSynchronouslyFromNonUIThread()
{
	ThreadPool.QueueUserWorkItem(delegate
	     {
	        CallInvokeSynchronouslyWithAction();
		TestComplete();
	     });
}

When running the test project we see the results in the browser window.

Unit Test results in browser window.
Figure: Browser view of Silverlight Test Run

Conclusion

For whatever reason, I believe the Silverlight team has taken the right approach with synchronous web service calls. While synchronous web service calls are not prohibited, they will cause ones application to become unresponsive if performed from the UI thread. Thus, we have seen that there is some missing infrastructure that really should be there to prevent us from stepping on our own toes. We have, however, come some way to address the problem in the included download.

In some ways we get the best of both worlds, blocking the UI thread is prohibited, yet we can still fallback to our familiar synchronous model when using background processing.

I would like to see the inclusion of a standard mechanism for performing synchronous web service calls from non-UI threads, as there is a legitimate case for them. Until then, please feel free to use the library provided with this article.

I hope you find this project useful. If so, then I'd appreciate it if you would rate it and/or leave feedback below. This will help me to make my next article better.

History

November 2008

  • Initial release.

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)


Written By
Engineer
Switzerland Switzerland
Daniel is a former senior engineer in Technology and Research at the Office of the CTO at Microsoft, working on next generation systems.

Previously Daniel was a nine-time Microsoft MVP and co-founder of Outcoder, a Swiss software and consulting company.

Daniel is the author of Windows Phone 8 Unleashed and Windows Phone 7.5 Unleashed, both published by SAMS.

Daniel is the developer behind several acclaimed mobile apps including Surfy Browser for Android and Windows Phone. Daniel is the creator of a number of popular open-source projects, most notably Codon.

Would you like Daniel to bring value to your organisation? Please contact

Blog | Twitter


Xamarin Experts
Windows 10 Experts

Comments and Discussions

 
AnswerRe: help me grok Obiwan - why not WaitOne()? Pin
Daniel Vaughan23-Jan-09 23:05
Daniel Vaughan23-Jan-09 23:05 
QuestionWhat could cause serviceType.GetMethod("BeginInitiateConnection") to return null? Pin
ARaees22-Jan-09 5:48
ARaees22-Jan-09 5:48 
AnswerRe: What could cause serviceType.GetMethod("BeginInitiateConnection") to return null? Pin
Daniel Vaughan22-Jan-09 7:56
Daniel Vaughan22-Jan-09 7:56 
GeneralRe: What could cause serviceType.GetMethod("BeginInitiateConnection") to return null? Pin
ARaees22-Jan-09 10:27
ARaees22-Jan-09 10:27 
GeneralRe: What could cause serviceType.GetMethod("BeginInitiateConnection") to return null? Pin
ARaees22-Jan-09 10:50
ARaees22-Jan-09 10:50 
GeneralRe: What could cause serviceType.GetMethod("BeginInitiateConnection") to return null? Pin
Daniel Vaughan22-Jan-09 20:04
Daniel Vaughan22-Jan-09 20:04 
GeneralRe: What could cause serviceType.GetMethod("BeginInitiateConnection") to return null? Pin
ARaees23-Jan-09 1:59
ARaees23-Jan-09 1:59 
GeneralCall anything Sync Pin
Dewey4-Jan-09 16:29
Dewey4-Jan-09 16:29 
Can this be used to call other things synchronously?

If so, do you have any examples?
GeneralRe: Call anything Sync Pin
Daniel Vaughan5-Jan-09 7:10
Daniel Vaughan5-Jan-09 7:10 
GeneralRe: Call anything Sync Pin
Dewey5-Jan-09 12:09
Dewey5-Jan-09 12:09 
GeneralRe: Call anything Sync Pin
Daniel Vaughan6-Jan-09 1:37
Daniel Vaughan6-Jan-09 1:37 
GeneralRe: Call anything Sync Pin
Dewey6-Jan-09 23:16
Dewey6-Jan-09 23:16 
GeneralRe: Call anything Sync Pin
digger6913-Jan-09 6:57
digger6913-Jan-09 6:57 
GeneralBrilliant article Pin
Dr.Luiji18-Dec-08 22:41
professionalDr.Luiji18-Dec-08 22:41 
GeneralRe: Brilliant article Pin
Daniel Vaughan19-Dec-08 8:30
Daniel Vaughan19-Dec-08 8:30 
QuestionI am getting TargetInvocationException while running this code [modified] Pin
kantaiah9-Dec-08 18:51
kantaiah9-Dec-08 18:51 
AnswerRe: I am getting TargetInvocationException while running this code Pin
Daniel Vaughan9-Dec-08 22:22
Daniel Vaughan9-Dec-08 22:22 
QuestionRe: I am getting TargetInvocationException while running this code Pin
kantaiah11-Dec-08 3:26
kantaiah11-Dec-08 3:26 
AnswerRe: I am getting TargetInvocationException while running this code Pin
Daniel Vaughan12-Dec-08 5:50
Daniel Vaughan12-Dec-08 5:50 
GeneralInterface definition and complex types Pin
gezanal26-Nov-08 1:10
gezanal26-Nov-08 1:10 
GeneralRe: Interface definition and complex types Pin
Daniel Vaughan26-Nov-08 12:20
Daniel Vaughan26-Nov-08 12:20 
GeneralRe: Interface definition and complex types Pin
gezanal26-Nov-08 22:17
gezanal26-Nov-08 22:17 
GeneralRe: Interface definition and complex types Pin
Daniel Vaughan27-Nov-08 0:08
Daniel Vaughan27-Nov-08 0:08 
QuestionCross-domain issue Pin
Juan Manuel Bastidas Bonilla25-Nov-08 7:18
Juan Manuel Bastidas Bonilla25-Nov-08 7:18 
AnswerRe: Cross-domain issue Pin
Daniel Vaughan25-Nov-08 9:34
Daniel Vaughan25-Nov-08 9:34 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.