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:
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 ;-)
If you´ve read my previous posts about why I deem WCF more of a problem than a solution and how I think we should switch to asynchronous only communication in distributed application, you might be wondering, how this could be done in an easy way.
Since a truely simple example to get started with WCF still is drawing quite some traffic to this blog, let me pick up on that and show you, how to accomplish the same but much easier with an async communication API.
For simplicities sake let me put all the code of client and server in just one file. In the following sections I´ll walk you through this file. The service it implements is simple. I start with a ping service. The client sends a text message to the server; the server dumps the text to the console.
The implementation is based on Microsoft´s CCR and two layers of abstraction encapsulating the CCR as well as WCF. See, I´m not saying WCF is bad per se; I just don´t want to use it in my application code. As a low level communication infrastructure the WCF is just great.
The two layers of abstraction on top of the CCR and WCF are the CCR Space and Xcoordination Application Space (or AppSpace for short). Both are open source. The CCR Space wraps CCR concepts and the AppSpace to make it easier to work with them. And the AppSpace enables you to use CCR ports for remote communication.
From now on I´ll refer to both as “the Spaces” or “Space based communication”.
If you want to follow along my samples download the binaries from the CCR Space project site and reference the CCRSpace.dll as well as microsoft.ccr.core.dll.
Service implementation
With the Spaces it´s easy to define a service. No special service contract is needed. You can use any method with just one parameter and no return value. The parameter type needs to be serializable, though. Use the [Seriablizable] attribute for your own message types.
1 using System;
2 using System.Threading;
3
4 using CcrSpaces.Core;
5
6 namespace AsyncSimple.Server
7 {
8 public class Program
9 {
10 public static void Ping(string name)
11 {
12 Console.WriteLine("SERVER - Processing Ping('{0}')", name);
13 }
Of course there are ways to define services with several methods (or to say it differently: which can process different message types). But for now let´s keep the service as simple as possible, like I did in the WCF sample.
Service hosting
Next you need to host the service. For that you create a CCR Space (CcrSpace) and configure it as a host using a certain transport layer (line 17). I´m using WCF with net.tcp binding as a transport, but I could also have used raw TCP sockets or WCF with named pipes or Jabber or MSMQ.
By passing “wcf.port=8000” to the configuration method the transport layer type is selected and the listening port specified.
15 public static void Main(object state)
16 {
17 using(var serverSpace = new CcrSpace().ConfigureAsHost("wcf.port=8000"))
18 {
19 var chService = serverSpace.CreateChannel<string>(Ping);
20 serverSpace.HostPort(chService, "Ping");
21
22 Console.WriteLine("SERVER - Running...");
23 Console.ReadLine(); // keep alive
24 }
25 }
Once the Space is running, the service is bound to a channel (line 19). The channel is used to send messages to the service.
To make the channel available for remote clients it´s hosted in the Space (line 20). This defines an address for the service listening on the channel. The service can now be reached over all registered transports by using their addresses combined with the service name; here that´s “localhost:8000” for WCF plus “Ping”: localhost:8000/Ping.
Is you leave out ConfigureAsHost() and HostPort() you get local communication instead of remote. With the Spaces it´s easy to switch between the two communication modes. The communication paradigm stays the same. No other changes are needed to your code. The only differene between local and remote async communication is hosting a port or not (and how you connect to a port depending on it being local or remote).
Client implementation
Since this is a self-contained example the client starts with kicking off the server:
30 namespace AsyncSimple.Client
31 {
32 class Program
33 {
34 static void Main(string[] args)
35 {
36 ThreadPool.QueueUserWorkItem(AsyncSimple.Server.Program.Main);
Then comes the important part: connecting the client to the server and calling the service:
38 using(var clientSpace = new CcrSpace().ConfigureAsHost("wcf.port=0"))
39 {
40 var chServiceProxy = clientSpace.ConnectToPort<string>("localhost:8000/Ping");
41
42 chServiceProxy.Post("world!");
43
44 Console.ReadLine(); // keep alive
45 }
Like the server the client needs to instantiate a CCR Space. Async communication is symmetric. So both parties communicating need to look the same. Both “are” Spaces.
Since the client does not publish any services, its port is of no interest so I set it to 0 for the AppSpace to choose. But of course the client Space needs to use the same transport layer like the server (line 38).
Then the client connects to the service´s remote port using its address (line 40). From now on the client can send messages to the service through the channel (line 42).
Summary
That´s it. This is how easy async communication can be. This is how easy remote communication can be. The application does not bother itself with complicated WCF configuration. Think of all the concepts and terms you need to understand for that, e.g. ServiceContract, OperationContract, ServiceBehaviour, InstanceContextMode, ServiceHost, and ChannelFactory.
With the Spaces you just need a CcrSpace and a Port (aka channel). Create a space, create a channel and bind your service methode to it, host/publish the channel on the server, connect to the channel on the client.
Once you grog the communication paradigm (async message oriented communication) it´s only 4 lines of code to set up service and client.
And now tell me: Can it become any easier, and stay true to the nature of remote communication? I doubt it.
Please get me right, I´m not saying those Spaces are a panacea or the ultimate communication API. I´m just challenging WCF´s position as the one-size-fits-all solution to remote communication. There are alternatives to WCF. Service busses like NServiceBus or Mass Transit are alternatives, but I think to use them seems to be an overkill for developers in many scenarios. That´s why I´m trying to show you a another alternative: Space based communication. It kind of looks like regular service communication, but it´s all asynchronous like the busses.
Ok, if this still sounds interesting to you, you sure have a couple of questions. How to do return a result from a service? How to notify clients of service progress? How to deal with service failures? Bear with me, I´ll try to answer them in the next couple of postings. For now I just wanted to show you how easy it is to get async remote communication running at all.