Nathan J Pledger

Program.X musings from the Isle of Man concerning ASP.NET, in particular accessibility, web standards and neat ideas.

Silverlight: Changing the synchronous mindset

Working with web sites, web pages, web services and web clients has until recently been very much a synchronous operation, which is despite the very asynchronous nature of the web. Synchronous programming techniques are linear in operation in that process A cannot execute until process B has completed. The very nature of the internet is that not only do you not know when you'll get a response from a server, you may not even get it at all, which can be particularly important when you don't own the server as many mash-up web applications do not. If you adopt a synchronous model, such as basic JavaScript or relying solely on the brower's threading model to load a page, you are likely to get freezes or missing portions of your web page. Asynchronous programming allows Process B to run alongside Process A. The two processes can run one after another, in any order, if at all. As a programmer, you need to work around the many headaches this can produce. Although, the experience for the end user is much improved as a result. Moving from a synchronous model to an asynchronous one can often be a challenge if you've never experienced this model previously.

I'm currently working in Silverlight, which forces users to use an asynchronous programming model, as per the NPAPI specification. This means that although a Silverlight component is very likely to need external data, it cannot rely on its timely execution or even a result. The traditional procedural programming pattern becomes redundant. If you have a complex and large data structure at your server, you certainly do not want to pull it all down to the client when you only need to use 5% of it. Likewise, you don't want to have to rely on lots of "little calls" to get the 5% of data on an "as needed" approach as the user navigates through the application.

The perfect example (which is a real example), is that of a Security Index. If you have a complex Security Index that identifies the permissions a user is has on actions and data, it would be tempting to have some code such as:

   1:  Permissions permissions=Security.GetPermissionsForUserOnItem(int itemID);

In a desktop environment, you could certainly get away with having that call as a function, which can either dig into an in-memory data structure, consult a database or even a web service. You can be reasonably sure that you will get a timely result, particularly for an Intranet application. This is not entirely reliable, and is certainly not recommended for developing over the internet using a platform such as Silverlight or similar client/server applications.

In a client/server environment, the GetPermissionsForUserOnItem() call would need to call back to a web service which may not respond as and when you expect. As Silverlight uses threading internally to generate calls to WCF web services, execution will fall out of the function almost immediately, without a result because the result will still be on its way back from the web server. Take the following code:

(Based on the excellent article by David Betz at http://www.netfxharmonics.com/2008/11/Understanding-WCF-Services-in-Silverlight-2)

   1:  public Permissions RequestPermissionsForItem(int itemId)
   2:  {
   3:       Permissions permissions=0;
   4:       ISecurityBroker securityBroker = UIContext.CreateWcfInterface<ISecurityBroker>("Security/SecurityBroker.svc");
   5:       // Point A: call to the web service
   6:       securityBroker.BeginGetPermittedActionsForDataItemAndUser(clientId, dataTypeId, dataItemId, result =>
   7:            {
   8:                 // Point B: This will only execute on response from the web service
   9:                 permissions = ((ISecurityBroker)result.AsyncState).EndGetPermittedActionsForDataItemAndUser(result);
  10:             }, securityBroker);
  11:        }
  12:        // Point C: Execution will immediately follow Point A, skipping Point B
  13:        return permissions;
  14:  }

This uses the following method, which in the example above in in the UIContext static class, which creates the web service (and represents my own code usage, you may have a more suitable mechanism):

   1:  public static class UIContext
   2:  {
   3:       public static TWcfApiEndPoint CreateWcfInterface<TWcfApiEndPoint>(string serviceUrl)
   4:       {
   5:            // create the binding elements
   6:            BinaryMessageEncodingBindingElement binaryMessageEncoding = new BinaryMessageEncodingBindingElement();
   7:            HttpTransportBindingElement httpTransport = new HttpTransportBindingElement() { MaxBufferSize = int.MaxValue, MaxReceivedMessageSize = int.MaxValue };
   8:            // add the binding elements into a Custom Binding
   9:            CustomBinding customBinding = new CustomBinding(binaryMessageEncoding, httpTransport);
  10:            // create the Endpoint URL 
  11:            EndpointAddress endpointAddress = new EndpointAddress(serviceUrl);
  12:            // create an interface for the WCF service
  13:            ChannelFactory<TWcfApiEndPoint> channelFactory=new ChannelFactory<TWcfApiEndPoint>(customBinding, endpointAddress);
  14:            // channelFactory.Faulted += new EventHandler(channelFactory_Faulted); - not implemented in this example
  15:            TWcfApiEndPoint client = channelFactory.CreateChannel();
  16:            return client;
  17:       }
  18:  } 

The comments illustrate that the web service call will be executed within its own thread (within the anonymous function), so Point B would not necessarily execute before point C. The result of permissions, therefore, will be 0. This rules out calls to functions as we've come to expect them to work.

There are ways to simulate synchronous behaviour, but as mentioned in Peter Bomberg's post from 2008, they are not at all a solution to an asynchronous problem. Classes such as ManualResetEvent and AutoResetEvent provide locking to prevent execution from a given point until a flag as been set on the object. The above code would become as follows:

   1:  public Permissions RequestPermissionsForItem(int itemId)
   2:  {
   3:       Permissions permissions=0;
   4:       ISecurityBroker securityBroker = UIContext.CreateWcfInterface<ISecurityBroker>("Security/SecurityBroker.svc");
   5:       // Point A: call to the web service
   6:       using (AutoResetEvent autoResetEvent=new AutoResetEvent(false))
   7:       {
   8:            securityBroker.BeginGetPermittedActionsForDataItemAndUser(clientId, dataTypeId, dataItemId, result =>
   9:            {
  10:                 // Point B: This will only execute on response from the web service
  11:                 permissions = ((ISecurityBroker)result.AsyncState).EndGetPermittedActionsForDataItemAndUser(result);
  12:                 // Point C: Sets a flag to signal that the result is ready
  13:                 autoResetEvent.Set();
  14:            }, securityBroker);
  15:            // Point D: Execution waits until the Set() method executes, signalling the result is ready
  16:            autoResetEvent.WaitOne();
  17:      }
  18:      return permissions;
  19:  }
 
This works - sort of. However, mixing this with user-interface based code, which runs inside its own thread, can soon result in threading deadlocks which will cause your application to stop ... dead. These are always difficult to resolve and fix in an elegant way. I often think, if it can't be fixed elegantly, you're probably doing it wrong.

So the problem remains; how can we request small bits of information from a potentially huge data source held behind a web service from a Silverlight (or similar) application? The challenge in this is not how to simulate a synchronous pattern, but how to match an appropriate callback pattern to your code.

Back to the example, we can provide an event, that is called when the result is ready. This will mean that the function will return void, and it causes added headaches at the consumer side of the function because the caller will need to wire-up an event handler, which synchronous patterns avoid. So we can change the code to:

   1:  public class PermissionsBroker
   2:  { 
   3:       public event EventHandler<RequestPermissionsForItemEventArgs> PermissionsForItemReady;
   4:   
   5:       protected virtual void OnPermissionsForItemReady(RequestPermissionsForItemEventArgs e)
   6:       {
   7:            if (PermissionsForItemReady != null) PermissionsForItemReady(this, e);
   8:       }
   9:   
  10:       public void RequestPermissionsForItem(int itemId)
  11:       {
  12:            Permissions permissions=0;
  13:            ISecurityBroker securityBroker = UIContext.CreateWcfInterface<ISecurityBroker>("Security/SecurityBroker.svc");
  14:            // Point A: call to the web service
  15:            securityBroker.BeginGetPermittedActionsForDataItemAndUser(clientId, dataTypeId, dataItemId, result =>
  16:            {
  17:                 // Point B: This will only execute on response from the web service
  18:                permissions = ((ISecurityBroker)result.AsyncState).EndGetPermittedActionsForDataItemAndUser(result);
  19:                OnPermissionsForItemReady(new RequestPermissionsForItemEventArgs() { ItemID = itemId, Permissions = permissions });
  20:            }, securityBroker);
  21:      }

Here, I have created a class, RequestPermissionsForItemEventArgs (not shown, but the properties are simple enough) that represents a means of passing a message back to the caller, with the permissions, when ready.

So the caller could:

   1:  PermissionsBroker permissionsBroker=new PermissionsBroker();
   2:  permissionsBroker.PermissionsForItemReady+=new EventHandler<RequestPermissionsForItemEventArgs>(SecurityCache_PermissionsForItemReady);
   3:  permissionsBroker.RequestPermissionsForItem(500);
   4:   
   5:  void SecurityCache_PermissionsForItemReady(object sender, RequestPermissionsForItemEventArgs e)
   6:  {
   7:       // results, act on permission here
   8:  }

That is a lot of lines just to check permissions. While this works, we need to reduce the headache for the consumer of the functionality by reducing the amount of plumbing they need to create just to get a simple value back from the server. By using an anonymous function, we can simplify this.

First, we use a delegate to allow us to pass a function as a parameter to the function. This will represent the callback operation:

   1:       public delegate void RequestPermissionsForItemDelegate(SecureDataActions secureDataActions);

We add this to our function:

   1:       public void RequestPermissionsForItem(int itemId, RequestPermissionsForItemDelegate requestPermissionsForItemDelegate)
   2:       {
   3:            Permissions permissions=0;
   4:            ISecurityBroker securityBroker = UIContext.CreateWcfInterface<ISecurityBroker>("Security/SecurityBroker.svc");
   5:            // Point A: call to the web service
   6:            securityBroker.BeginGetPermittedActionsForDataItemAndUser(clientId, dataTypeId, dataItemId, result =>
   7:            {
   8:                 // Point B: This will only execute on response from the web service
   9:                permissions = ((ISecurityBroker)result.AsyncState).EndGetPermittedActionsForDataItemAndUser(result);
  10:                requestPermissionsForItemDelegate(permissions);
  11:            }, securityBroker);
  12:      }

And using an anonymous function (using a lambda), we can call it and get the result in just 5 lines of code:

   1:  RequestPermissionsForItem(itemID, permissions =>
   2:       {
   3:               bool canWrite = permissions!=0;
   4:       }
   5:  );

The important point to remember is that the code executing within the anonymous function is a in its own scope, and therefore cannot pass its result out of the anonymous function other than by setting a higher-scoped variable (but again you need to consider asynchronous behaviour) and that it will not be executing on the UI thread so will need to be invoked from the dispatcher accordingly.

Leave a Comment

(required) 

(required) 

(optional)

(required) 


Please add 5 and 7 and type the answer here: