Doing asynchronous distributed request/response service calls without WCF

In my previous blog post I hopefully was able to demonstrate how low the entry barrier is to asynchronous remote communication. It´s as easy as hosting a service like this

   10 using(var serverSpace = new CcrSpace().ConfigureAsHost("wcf.port=8000"))

   11 {

   12     serverSpace.HostPort(

   13                     serverSpace.CreateChannel<string>(t => Console.WriteLine(t)),

   14                     "MyService");

and connecting to such a service like this:

   16 using(var clientSpace = new CcrSpace().ConfigureAsHost("wcf.port=0"))

   17 {

   18     var s = clientSpace.ConnectToPort<string>("localhost:8000/MyService");

   19 

   20     s.Post("hello, world!");

Under the hood this is net.tcp WCF communication like Microsoft wants you to do it. But on the surface the CCR Space in conjunction with the Xcoordination Application Space provides you with an easy to use asynchronous API based on *Microsoft´s Concurrency Coordination Runtime.

Request/Response

Calling a service and not expecting a response, though, is not what you want to do most of the time. Usually you call a service to have it process some data and return a result to the caller (client). So the question is, how can you do this in an asynchronous communication world? WCF and the other sync remote communication APIs make that a no brainer. That´s what you love them for. So in order to motivate you to switch to an async API I need to prove that such service usage won´t become too difficult, I guess.

What do you need to do to have the server not only dump the message you sent it to the console, but to return it to the caller? How to write the most simple echo service? Compared to the ping service you saw in my previous posting this is not much of a difference. Here´s the service implementation:

   11 public static string Echo(string text)

   12 {

   13     Console.WriteLine("SERVER - Processing Echo Request for '{0}'", text);

   14     return text;

   15 }

Yes, that´s right. It´s a method with a return value like you would have used it in a WCF service. Just make sure the request and the response message types are [Serializable].

And this is the code to host this service:

   19 using(var serverSpace = new CcrSpace().ConfigureAsHost("wcf.port=8000"))

   20 {

   21     var chService = serverSpace.CreateChannel<string, string>(Echo);

   22     serverSpace.HostPort(chService, "Echo");

The only difference is in the two type parameters to CreateChannel(). They specify this is request/response channel. The first type is for the request type, the second for the response type.

When does it start to become difficult, you might ask? Async communication is supposed to be difficult. Well, check out the client code. First it needs to connect to the service:

   40 using(var clientSpace = new CcrSpace().ConfigureAsHost("wcf.port=0"))

   41 {

   42     var chServiceProxy = clientSpace.ConnectToPort<string, string>("localhost:8000/Echo");

This isn´t difficult, or is it? Like before for the one-way ping service the client just needs to specify the address of the service. But of course the local channel needs to match the remote channel´s signature. So the client passes in two type parameters for the request and response message types.

And now the final part: sending the remote service a message and receiving a response. Sure, this looks a bit different from your usual remote function call. But I´d say it´s not all too inconvenient:

   44 chServiceProxy

   45     .Request("hello!")

   46     .Receive(t => Console.WriteLine("Answer from echo service: {0}", t));

You send the request by calling the Request() method with the request message. Then you wait for the response using the Receive() method passing it a continuation. It´s a method to be called once the response arrives at the caller.

Remember: This is asynchronous communication. That means the code after the Receive() will be very likely executed before (!) that of the continuation. So if you want to have a conversation between client and service you´d need to be a bit careful how you model it. Don´t just write channel.Request().Receive() statements one after another. But let that be a topic for a future posting.

For now I  hopefully was able to show you how easy request/response communication can be even in the async world. There are of course other ways to accomplish this, but here I wanted to show you the most simple way so you can get started without jumping through too many mental async hoops.

Request/Response in WinForms Applications

This is all well and good – but it won´t work in your WinForms application. Because when the response arrives it arrives on a different thread than the request had been issued from. And that means you can just simply display the result in a WinForms control. That can only be done on the WinForms thread.

I take up the challenge and demonstrate to you this is easy too using the CCR Space. Check out this tiny WinForms application:

image

Whatever you type into the text box gets sent to the echo service and returned in upper case letters only. You can guess how easy such a service will look:

   17 using (var serverSpace = new CcrSpace().ConfigureAsHost("wcf.port=8000"))

   18 {

   19     serverSpace.HostPort(

   20                     serverSpace.CreateChannel<string, string>(t => t.ToUpper()),

   21                     "Echo");

Also the client is as easy as before:

   23 using (var clientSpace = new CcrSpace().ConfigureAsHost("wcf.port=0"))

   24 {

   25     var chService = clientSpace.ConnectToPort<string, string>("localhost:8000/Echo");

But then to let the client form know about the service, the service channel is injected like any other ordinary dependency:

   29 Application.Run(new Form1(chService));

   15 public partial class Form1 : Form

   16 {

   17     private Port<CcrsRequest<string, string>> echoService;

   18 

   19     public Form1(Port<CcrsRequest<string, string>> echoService)

   20     {

   21         InitializeComponent();

   22 

   23         this.echoService = echoService;

   24     }

And how does the client form interact with the service so there will be no cross-thread calling problem?

   26 private void button1_Click(object sender, EventArgs e)

   27 {

   28     this.echoService

   29             .Request(this.textBox1.Text)

   30             .Receive(t => listBox1.Items.Add(t),

   31                      CcrsHandlerModes.InCurrentSyncContext);

   32 }

It´s the same pattern as above: call Request() on the channel and append Receive() to this call. However, as the final parameter to Receive() pass in CcrsHandlerModes.InCurrentSyncContext. This advises the client Space to switch to the WinForms synchronization context before executing the response message handler.

Notice, however, the signature of the remote service port. It´s Port<CcrsRequest<TRequest, TResponse>>. By wrapping the application message in a CcrsRequest<,> message the channel.Request().Receive() magic is possible.

Summary

Asynchronous communication certainly is different from synchronous. It takes some time wrap your head around it. And you certainly don´t want to start with it slinging threads yourself. But with a pretty high level async API like the CCR and the CCR Space provides, it´s not beyond the average developers reach, I´d say.

What´s next? I guess you want to know how errors are handled in distributed async scenarios. Let that be the topic of my next posting. Until then trust me: It´s much easier than with WCF ;-)

6 Comments

  • I've had my own fair share of frustration with WCF, but I don't really see where your article shows how your approach is easier or better.

  • @Anon: Well, to understand my main point you need to believe in async communication to be fundamental for distributed scenarios.

    As long as you think, sync communication is ok and scales well, you´re probably content with WCF etc.

    But also then... don´t you feel the pain when setting up just simple communication patterns like notifications with WCF? At least I feel burdend by all those concepts and details I need to think about. Simple things are not really simple with WCF. They might be simpler than with other technologies. But are they really simple? Even for a sync communication API WCF does not make it truely easy to do all the simple stuff like exception handling or reentrance. You (!) need to take of them by making some decisions even in trivial scenarios.

    Since I´m solely doing async communication in distributed apps I feel much less burdend. But maybe that´s because my fundamental outlook onto the world is different from yours? I like Functional Programming and I´m even doing event-based architectures for synchronous components within my desktop clients. My architectures have become easier to maintain by that.

    -Ralf

  • "Well, to understand my main point you need to believe in async communication to be fundamental for distributed scenarios."

    I do, or at least I think I do.

  • @Anon: Hm... you do? Then let me know in what regard you find WCF easy to use for the agreed upon necessary async communication?

  • I didn't say it was easy, if I thought it was easy, I probably wouldn't read about alternates.

    Best of luck to you.

  • hi ralf - how do I download ccr space? I don't see a zip file that contains the project and binaries on google code.

    thanks,
    tom

Comments have been disabled for this content.