BeginInvoke Bugs

A delegate is a special type that can be bound at execution time to a method invocation. Normally you'd think of method invocations as being synchronous, but delegates can be executed either synchronously in the obvious way or asynchronously by introducing an extra thread of execution. An asynchronous delegate invocation uses the standard BeginInvoke and EndInvoke pattern, with the option to provide a callback for when the work is complete. You would expect that the asynchronous delegate pattern would be an easy way to make asynchronous calls to a web service. You would be wrong.

 [ServiceContract]
public interface IService
{
   [OperationContract]
   string Echo(string text);
}

public class Service : IService
{
   public string Echo(string text)
   {
      Thread.Sleep(5000);      
      return text;
   }
}

public delegate string EchoDelegate(string text);

public class BeginInvokeDelegate
{
   static void Callback(IAsyncResult result)
   {
      Console.WriteLine("In callback.");
   }

   static void Main(string[] args)
   {
      string uri = "localhost:8000/";
      ServiceHost service = new ServiceHost(typeof(Service));
      service.AddServiceEndpoint(typeof(IService), new BasicHttpBinding(), uri);
      service.Open();

      ChannelFactory<IService> factory = new ChannelFactory<IService>(new BasicHttpBinding(), new EndpointAddress(uri));
      IService proxy = factory.CreateChannel();

      EchoDelegate d = new EchoDelegate(proxy.Echo);
      IAsyncResult result = d.BeginInvoke("foo", new AsyncCallback(Callback), null);
      Console.WriteLine("Returned.");
      Console.WriteLine(d.EndInvoke(result));

      factory.Close();
      service.Close();
      Console.ReadLine();
   }
}

The code above is using a ChannelFactory against a synchronous version of the Echo interface. The client and service are decoupled so it would have been legal to generate a client proxy that expressed the Echo interface using an asynchronous representation regardless of how Echo is implemented on the server. Instead, the code attempts to make the service call asynchronous by wrapping it in a delegate and using BeginInvoke. If you run this code, then you'll see that the call to BeginInvoke does not complete until after the Echo service call has returned. This defeats the purpose of using the asynchronous pattern. The problem is that BeginInvoke knows about and only works with specific types of proxy objects, which do not include the proxy objects generated by ChannelFactory. The right way to make an asynchronous service call is to generate a proxy that has an asynchronous representation of the service method.

Next time: Logging Binary Messages