Click here to Skip to main content
15,867,568 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

 
GeneralRe: SL3 Functionality Pin
Daniel Vaughan25-Feb-10 5:58
Daniel Vaughan25-Feb-10 5:58 
QuestionTrying to convert this to VB Pin
Dave Massaro3-Jan-10 5:48
Dave Massaro3-Jan-10 5:48 
AnswerRe: Trying to convert this to VB Pin
Dave Massaro6-Jan-10 9:00
Dave Massaro6-Jan-10 9:00 
QuestionDo you think, that is possibble to call a DataServiceQuery synchronized ? Pin
Heribert Bennek9-Dec-09 9:37
Heribert Bennek9-Dec-09 9:37 
AnswerRe: Do you think, that is possibble to call a DataServiceQuery synchronized ? Pin
Daniel Vaughan9-Dec-09 11:45
Daniel Vaughan9-Dec-09 11:45 
GeneralRe: Do you think, that is possibble to call a DataServiceQuery synchronized ? Pin
Heribert Bennek9-Dec-09 20:08
Heribert Bennek9-Dec-09 20:08 
QuestionDoes this code still work with SL3? Pin
Chris D'Arrigo10-Oct-09 16:35
Chris D'Arrigo10-Oct-09 16:35 
AnswerRe: Does this code still work with SL3? Pin
Daniel Vaughan10-Oct-09 23:25
Daniel Vaughan10-Oct-09 23:25 
Hi Chris,

To double check, I downloaded the source, extracted and opened the solution, converted it, set the startup project to the Web application, debugged and it appears to works as expected with SL3.

Cheers,
Daniel

Daniel Vaughan
Blog: DanielVaughan.Orpius.com


Company: Outcoder

GeneralRe: Does this code still work with SL3? Pin
Chris D'Arrigo11-Oct-09 2:19
Chris D'Arrigo11-Oct-09 2:19 
GeneralRe: Does this code still work with SL3? Pin
Chris D'Arrigo11-Oct-09 2:54
Chris D'Arrigo11-Oct-09 2:54 
GeneralRe: Does this code still work with SL3? Pin
Daniel Vaughan11-Oct-09 4:05
Daniel Vaughan11-Oct-09 4:05 
GeneralRe: Does this code still work with SL3? Pin
Chris D'Arrigo11-Oct-09 4:16
Chris D'Arrigo11-Oct-09 4:16 
GeneralRe: Does this code still work with SL3? Pin
Daniel Vaughan11-Oct-09 7:51
Daniel Vaughan11-Oct-09 7:51 
GeneralRe: Does this code still work with SL3? Pin
Daniel Vaughan12-Oct-09 1:47
Daniel Vaughan12-Oct-09 1:47 
QuestionIs it possible to send a Disconnect message when the browser is closing? Pin
John Flora4-Sep-09 9:20
John Flora4-Sep-09 9:20 
AnswerRe: Is it possible to send a Disconnect message when the browser is closing? Pin
Daniel Vaughan4-Sep-09 10:15
Daniel Vaughan4-Sep-09 10:15 
QuestionConnecting to external web service Pin
apachia26-Aug-09 23:55
apachia26-Aug-09 23:55 
GeneralThanks Pin
Tom Klein2-Aug-09 23:36
Tom Klein2-Aug-09 23:36 
GeneralRe: Thanks Pin
Daniel Vaughan3-Aug-09 0:11
Daniel Vaughan3-Aug-09 0:11 
GeneralThere is a way for simple synchronization Pin
StLaEmpira2-Aug-09 6:55
StLaEmpira2-Aug-09 6:55 
GeneralRe: There is a way for simple synchronization Pin
Daniel Vaughan3-Aug-09 0:09
Daniel Vaughan3-Aug-09 0:09 
GeneralNice workaround for a ludicrous design. [modified] Pin
Member 31361511-Jul-09 18:16
Member 31361511-Jul-09 18:16 
GeneralRe: Nice workaround for a ludicrous design. Pin
Daniel Vaughan12-Jul-09 0:26
Daniel Vaughan12-Jul-09 0:26 
QuestionReturn Values Pin
Adriaan Davel9-Jul-09 1:40
Adriaan Davel9-Jul-09 1:40 
AnswerRe: Return Values Pin
Adriaan Davel9-Jul-09 2:05
Adriaan Davel9-Jul-09 2:05 

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.