Long ago I wrote an Example on threading with the WinApi using Delphi, now we have AsyncCallBacks and managed threading. But since a few months we also have the CCR and take my word for it, this baby rocks. I will supply you guys with another run of the "Really Simple Sample" series and now it will be on the CCR. Here goes the first sample.
First off all I tried to build a winforms application running in the CCR way, I used the Ccr.Adaptors.WinForms namespace to accomplish this in a simple way. Just create a new WinForms application and change the Program class like follows.
static class Program {
//Expose the dispatcher queue for use in all your forms
public static DispatcherQueue DQueue {
get {
return _DispatcherQueue;
}
}
//Expose the WinFormsService Port for use in all your forms
public static WinFormsServicePort WFServicePort {
get {
return _WFServicePort;
}
}
private static DispatcherQueue _DispatcherQueue;
private static WinFormsServicePort _WFServicePort;
[STAThread]
static void Main() {
System.Threading.ManualResetEvent MainThreadEvent = new System.Threading.ManualResetEvent(false);
// Create the Dispatcher that will execute our delegates that we add to the
// DispatcherQueue
using (Dispatcher dispatcher = new Dispatcher(0, "BlogExample Dispatcher")) {
_DispatcherQueue = new DispatcherQueue("BlogExample DispatcherQueue", dispatcher);
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
// Bind the DispatcherQueue to the WinformsAdaptor and
// create the WinFormServicePort.
_WFServicePort = WinFormsAdaptor.Create(_DispatcherQueue);
// Use the WinFormsServicePort to create a MainForm.
// We use a delegate to create the form on the right thread and
// bind a delegate to the HandleDestroy event of the form to let the
// application stop when the MainForm closes.
_WFServicePort.Post(new RunForm(
delegate { Form1 form = new Form1();
form.HandleDestroyed += delegate(object sender, EventArgs e) { MainThreadEvent.Set(); };
return form;
}
));
// Wait for the form to be closed, the WinFormsAdaptor is taking
// care of the Application.Run in its own thread, we just need
// to make sure the application keeps on running by blocking the
// current thread.
MainThreadEvent.WaitOne();
// We need to inform the WinFormsAdaptor that it is oke to quit.
_WFServicePort.Post(new Shutdown());
}
}
}
Now that we have a running form, let's build a sample that queries a webservice several times with different parameters and show the result in a listbox, off course async. Add a listbox and a button to the MainForm and write the following code.
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
//The port that receives the responses of the webservices.
private Port _ResponsePort;
//The port that receives any exceptions of the requests to the webservices.
private Port _FailurePort;
// A simple method to call a webservice async and fill our ServiceResponse instance with
// the city.
private void CallTemperatureServiceAsync(localhost.TemperatureService service, string city) {
ServiceResponse response = new ServiceResponse();
response.city = city;
service.GetTemperatureAsync(city, response);
}
private void button1_Click(object sender, EventArgs e) {
localhost.TemperatureService temperatureService = new localhost.TemperatureService();
//Create the ports
_ResponsePort = new Port();
_FailurePort = new Port();
// Define the GetTemperatureCompleted event. We use this delegate to post to the correct port.
temperatureService.GetTemperatureCompleted += new CCRBlogExample.localhost.GetTemperatureCompletedEventHandler(temperatureService_GetTemperatureCompleted);
// Make the calls to the webservice.
CallTemperatureServiceAsync(temperatureService,"Brussels");
CallTemperatureServiceAsync(temperatureService,"Antwerp");
CallTemperatureServiceAsync(temperatureService,"Ghent");
// And here it happens, this is the cool stuff here.
// We use an Interleave Arbiter to coordinate the results of
// the webservicerequest. We define here that we should stop all processing
// when there is one exception on the requests, so even if you call the webservice
// 100 times, any excpetion will stop the processing.
// Furthermore we define here we can handle all request concurrently, if we would access
// non-threadsafe resources we could put them in the ExclusiveReceiverGroup and the CCR
// will make sure there is only one thread handling them.
Arbiter.Activate(Program.DQueue, Arbiter.Interleave(
new TeardownReceiverGroup(
Arbiter.Receive(false, _FailurePort,
delegate(Exception ex) { })),
new ExclusiveReceiverGroup(),
new ConcurrentReceiverGroup(
Arbiter.Receive(true, _ResponsePort,
delegate(ServiceResponse response) {
Program.WFServicePort.FormInvoke(delegate {
ResultListBox.Items.Add(response.city + ":" + response.Temperature.ToString());
});
}))));
}
//Here we handle all posting to the correct ports depending on the output of the async request of the
//webservice.
void temperatureService_GetTemperatureCompleted(object sender, CCRBlogExample.localhost.GetTemperatureCompletedEventArgs e) {
if (e.Cancelled) {
//Post exception;
_FailurePort.Post(new Exception("cancelled"));
return;
}
if (e.Error != null) {
//Post exception;
_FailurePort.Post(e.Error);
return;
}
//Post ServiceResponse
ServiceResponse response = e.UserState as ServiceResponse;
response.Temperature = e.Result;
_ResponsePort.Post(response);
}
}
// A simple class to transport data and to use
// in the Ports.
public class ServiceResponse {
public int Temperature;
public string city;
}
What I like most is the possibility to use local variables in other threads. Like you see in the RunForms delegate. You don't need to declare them somewhere global, they are defined local and your code stays really clean and readable.
This is cool stuff, easy to use and easy to follow. Think of all projects you did with threading and look at this. The next sample will be a sample on how to program async in a sequential way. A feature of the CCR, that will change the way we program async.