In a previous post I proposed an example about using a Polling Duplex service to notify a number of client about events happening to a common resource. In that complex example I did show a service that runs a thread that monitors the resource. Every time the resource is changed the thread detects the change and notifies a list of connected clients to display a sort of bus station timetable. This kind of service handles only one of the possible problems you can solve using the PollingDuplex.
Imagine you have a long task to accomplish and need to get updates from the webserver that is running the activity. This task may be an import or parsing of a complex file. Doing this activity with a normal page or with a webservice may incur in timeouts and obviously leave the user waiting the end of the task without a visible notification.
The solution attached to this post is an example of this case. I've created a service that is able to ping various web services to simulate a long-running task. You can change the logic in the core of the service, but this is not the point. The service is able to return updated status while it is processing the hosts it has to ping. Here is a screenshot of the interface.
How it works
When you configure a service to be a Polling Duplex endpoint you are implicitly stating you will call its methods using a one-way paradigm. If you watch the code attached at the end of the article you may find that the methods of the service are all decorated with the IsOneWay property setted to true.
1: [ServiceContract(
2: Namespace = "http://silverlightplayground.org/polling",
3: CallbackContract = typeof(IPollingServiceClient))]
4: public interface IPollingService
5: {
6: [OperationContract(IsOneWay=true)]
7: void PingHosts(string[] hostsToPing);
8: }
In a normal WCF service, saying a method is one-way imply that when you call it you have not to wait the end of its works. It will be accomplished asynchronously and you can forget the call and move forward to another activity. The runtime of WCF will start a thread for the call and it will run the task to complete.
In a polling duplex scenario the things act in a subtly different way. After the call has been started the client does not disconnect itself until a predefined timeout has expired. This timeout is something usual for a web server where every call need to be completed in a given amount of time for resource sharing reasons. But when the timeout expires the polling duplex client begins another connection and wait again for another timeout. This happen an indefinite number of times until the service or the client decide to stop the connection.
During this time the service thread owns an handle to the polling duplex client. This handle is called callback contract and is useful to the service to give notification to the client when something happen. When the service call a callback method the parts are swapped for a while so the server become a client and the client a server. What really happen in and http scenario is that the client is notified of the callback and it immediately disconnect from the webserver without waiting the next incoming timeout.
The flow I described means that the method called runs in a completely separated thread and is able to perform long-running operaton. So In my example I simply started a for-each loop to call the ping procedure once for every host I need to control.
1: /// <summary>
2: /// Does the task.
3: /// </summary>
4: /// <param name="argument">The argument.</param>
5: public void PingHosts(string [] hostsToPing)
6: {
7: IPollingServiceClient client =
8: OperationContext.Current.GetCallbackChannel<IPollingServiceClient>();
9:
10: foreach (string host in hostsToPing)
11: {
12: try
13: {
14: IPHostEntry pingTarget = Dns.GetHostByName(host);
15: IEnumerable<PingResult> results = pingTarget.Ping(10);
16: this.NotifyResult(client, host, results);
17: }
18: catch (Exception)
19: {
20: throw;
21: }
22: }
23: }
Ever time a ping sequence is completed I notify the results to the client using the NotifyResults method on the clients. Once the method received the total expected results it is responsible of dropping the connection calling CloseAsync(). I you do not call this method the service continue the polling but there is not any working thread.
1: if (progress.Value == this.Hosts.Count + 1)
2: {
3: this.SetButton("Run", true);
4: this.Client.CloseAsync();
5: }
Download: SilverlightPlayground.LongRunningTasks.zip (1,42MB)