Invoking and Implementing Operations Asynchronously


A OneWay operation is useful for “fire and forget” scenarios, where the client application does not expect the service to pass back any information. However, many operations do not fit into this scheme of working and return data to the client application. To cater for these situations, WCF supports asynchronous operations and the IAsyncResult design pattern. WCF enables you to implement the IAsyncResult design pattern in two ways: in the client application invoking the operation and in the WCF service implementing the operation.

More Info 

The IAsyncResult design pattern is commonly used throughout the .NET Framework and is not peculiar to WCF. For details, see the topic “Asynchronous Programming Design Patterns” in the .NET Framework Developer’s guide, available in the Microsoft Visual Studio 2005 Documentation, and also online at http://msdn2.microsoft.com/en-us/library/ms228969.aspx.

Invoking an Operation Asynchronously in a Client Application

WCF enables you to generate a version of the proxy class that a client application can use to invoke operations asynchronously; you can use the /async flag with the svcutil utility when creating the proxy class. When you specify this flag, svcutil generates begin and end pairs of methods for each operation. The client application can invoke the begin method to initiate the operation. The begin method returns after sending the request, but a new thread created by the .NET Framework runtime in the client waits for the response. When you invoke the begin method, you also provide the name of a callback method. When the service finishes the operation and returns the results to the client proxy, the new thread executes this callback method. You use the callback method to retrieve the results from the service. You should also call the end method for the operation to indicate that you have processed the response.

It is important to understand that you do not need to modify the service in any way to support this form of asynchronous programming. Indeed, the service itself does not necessarily have to be a WCF service; it could be a service implemented by using other technologies. The code making the operation appear asynchronous to the client application is encapsulated inside the proxy generated on the client side and the .NET Framework runtime. All the threading issues are handled by code running in the WCF runtime on the client. As far as the service is concerned, the operation is being invoked in the exact same, synchronous manner that you have seen in all the preceding chapters in this book.

Implementing an Operation Asynchronously in a WCF Service

As mentioned earlier, WCF also enables you to implement an operation that can execute asynchronously. In this case, the service provides its own pair of begin and end methods that constitute the operation. The code in the client application invokes the operation through the proxy object using the ordinary operation name (not the begin method). The WCF runtime transparently routes the operation to the begin method, so the client application is not necessarily aware that the service implements the operation as an asynchronous method.

As a variation, the developer of the service can add logic to the begin method to choose whether the operation should run synchronously or asynchronously. For example, if the current workload of the service is light, it might make sense to perform the operation synchronously to allow it to complete as soon as possible. As the workload increases, the service might choose to implement the operation asynchronously. Implementing operations in this manner in a service can increase the scalability and performance of a service without the need to modify client applications. It is recommended that you implement any operation that returns data to a client application after performing a lengthy piece of processing in this way.

Important 

You should understand the important distinction between asynchronous operation invocation in the client application and asynchronous operation implementation in the service. Asynchronous invocation in the client application enables the client to initiate the operation and then continue its own processing while waiting for a response. Asynchronous implementation in the service enables the service to offload the processing to another thread, or sleep while waiting for some background process to complete. A client application invoking an operation implemented asynchronously in the service still waits for the operation to complete before continuing.

You can specify that an operation supports asynchronous processing by setting the AsyncPattern property to true in the OperationContract attribute when defining the operation and providing a pair of methods that follow a prescribed naming convention and signature and that implement the IAsyncResult design pattern.

In the next set of exercises, you will add another operation called CalculateTotalValueOfStock to the AdventureWorksAdmin service. The purpose of this operation is to determine the total value of every item currently held in the AdventureWorks warehouse. This operation could take a significant time to run, so you will implement it as an asynchronous method.

Add an asynchronous operation to the AdventureWorks administrative service

  1. In Visual Studio 2005, edit the image from book Service.cs file in the App_Code folder in the C:\\AdventureWorksAdmin\ project. Add the following operation, shown in bold, to the IAdventureWorksAdmin service contract:

     [ServiceContract(Namespace="http://adventure-works.com/2007/01/01",                  Name="AdministrativeService")] public interface IAdventureWorksAdmin {     [OperationContract(IsOneWay = true)]     void GenerateDailySalesReport(string id);          [OperationContract(AsyncPattern = true)]     IAsyncResult BeginCalculateTotalValueOfStock(string id,                                                  AsyncCallback cb,                                                  object s);     int EndCalculateTotalValueOfStock(IAsyncResult r); }

    This operation consists of two methods: BeginCalculateTotalValueOfStock and EndCalculateTotalValueOfStock. Together, they constitute a single asynchronous operation called CalculateTotalValueOfStock. It is important that you name both methods in the operation following this convention for them to be recognized correctly when you build the client proxy. You can specify whatever parameters the operation requires in the begin method (in this case, the client application will pass in a string parameter to identify each invocation of the operation), but the final two parameters must be an AsyncCallback referencing a callback method in the client application and an object holding state information provided by the client application. The return type must be IAsyncResult. The end method must take a single parameter of type IAsyncResult, but the return type should be the type appropriate for the operation. In this case, the CalculateTotalValueOfStock operation returns an int containing the calculated value.

    The other key part of this operation is the AsyncPattern property of the OperationContract attribute. You only apply the OperationContract attribute to the begin method. When you generate the metadata for this service (when building the client proxy, for example), this property causes the begin and end methods to be recognized as the implementation of a single asynchronous operation.

  1. In Solution Explorer, right-click the App_Code folder in the C:\\AdventureWorksAdmin\ project and then click Add Existing Item. Add the file image from book AsyncResult.cs, located in the Microsoft Press\WCF Step By Step\Chapter 11\Async folder under your \My Documents folder.

  2. Open the image from book AsyncResult.cs file and examine its contents. It contains a single generic class called AsyncResult that implements the IAsyncResult interface. Detailed discussion of this class and the IAsyncResult interface is outside the scope of this book, but the purpose of the AsyncResult class is to provide synchronization methods and state information required by other classes that implement asynchronous methods. For this exercise, the important members of the AsyncResult class are:

    • Data. This property provides access to the data returned by the asynchronous operation. In this example, the CalculateTotalValueOfStock operation will populate this property and return the AsyncResult object to the client application when it executes the end method.

    • AsyncResult. This is the constructor. It take two parameters which it stores in private fields. The service will use the synchronous parameter to indicate whether it really is invoking the operation synchronously, and the stateData parameter will be a reference to the object passed in as the final parameter to the begin method (it is important to save this object, as it has to be returned to the client application to enable it to complete processing).

  3. Return to the image from book Service.cs file in the App_Code folder. Add the following delegate to the AdventureWorksAdmin class:

     private delegate void AsyncSleepCaller(int millisecondsTimeout);

    You will use this delegate in the methods that you will add in the next steps.

  4. Add the following method to the AdventureWorksAdmin class:

     // CalculateTotalValueOfStock operation // Service can elect to perform the operation // synchronously or asynchronously public IAsyncResult BeginCalculateTotalValueOfStock(string id,     AsyncCallback callback, object state) {     AsyncResult<int> calcTotalValueResult;     // Generate a random number.     // The value generated determines the "complexity" of the operation         Random generator = new Random();     // If the random number is even, then the operation is simple     // so perform it synchronously     if ((generator.Next() % 2) == 0)     {         calcTotalValueResult = new AsyncResult<int>(true, state);         System.Threading.Thread.Sleep(20000);         System.Windows.MessageBox.Show(             "Synchronous sleep completed");         calcTotalValueResult.Data = 5555555;         calcTotalValueResult.Complete();     }     // Otherwise, the operation is complex so perform it asynchronously     else     {         // Perform the operation asynchronously         calcTotalValueResult = new AsyncResult<int>(false, state);         AsyncSleepCaller asyncSleep = new             AsyncSleepCaller(System.Threading.Thread.Sleep);         IAsyncResult result = asyncSleep.BeginInvoke(30000,             new AsyncCallback(EndAsyncSleep), calcTotalValueResult);     }     callback(calcTotalValueResult);     System.Windows.MessageBox.Show(         "BeginCalculateTotalValueOfStock completed for " + id);     return calcTotalValueResult; }

    Note 

    You can find this code in the file image from book BeginCalculateTotalValueOfStock.txt in the Async folder under the Chapter 11 folder.

    Again, the exact details of how this method works are outside the scope of this book (strictly speaking, it has nothing to do with WCF). But to summarize, the method generates a random number, and if this number is even it performs the operation synchronously, otherwise it performs it asynchronously. In the synchronous case, the code creates a new AsyncResult object, sleeps for 20 seconds to simulate the time taken to perform the calculation, and then populates the AsyncResult object with the result–5555555. In the asynchronous case, the code also creates an AsyncResult object, but spawns a thread that sleeps for 30 seconds in the background. It does not populate the AsyncResult object, as this happens when the background, when the sleeping thread, wakes up later. In both cases, the code invokes the callback method in the client application, passing the AsyncResult object as its parameter. The client application will retrieve the results of the calculation from this object. The same AsyncResult object is also returned as the result of this method (this is a requirement of the IAsyncResult design pattern).

    The method also displays message boxes helping you to trace the execution of the method and establish whether the operation is running synchronously or asynchronously.

  5. Add the end method shown below to the AdventureWorksAdmin class:

     public int EndCalculateTotalValueOfStock(IAsyncResult r) {     // Wait until the AsyncResult object indicates the     // operation is complete     AsyncResult<int> result = r as AsyncResult<int>;     if (!result.CompletedSynchronously)     {         System.Threading.WaitHandle waitHandle = result.AsyncWaitHandle;         waitHandle.WaitOne();     }     // Return the calculated value in the Data field     return result.Data; }

    Note 

    You can find this code in the file image from book EndCalculateTotalValueOfStock.txt in the Async folder under the Chapter 11 folder.

    This method is invoked when the begin method completes. The purpose of this method is to retrieve the result of the calculation from the Data property in the AsyncResult object passed in as the parameter. If the operation is being performed asynchronously, it might not have completed yet. (Applications invoking the begin method for an asynchronous operation can call the end method at any time after the begin finishes, so the end method should ensure that the operation has completed before returning.) In this case, the method waits until the AsyncResult object indicates that the operation has finished before extracting the data and returning.

  6. Add the following method to the AdventureWorksAdmin class:

     private void EndAsyncSleep(IAsyncResult ar) {     // This delegate indicates that the "complex" calculation     // has finished     AsyncResult<int> calcTotalValueResult =         (AsyncResult<int>)ar.AsyncState;     calcTotalValueResult.Data = 9999999;     calcTotalValueResult.Complete();     System.Windows.MessageBox.Show("Asynchronous sleep completed"); }

    Note 

    You can find this code in the file image from book EndAsyncSleep.txt in the Async folder under the Chapter 11 folder.

    If the begin method decides to perform its task asynchronously, it simulates performing the calculation by creating a new thread and sleeping for 30 seconds. The EndAsyncSleep method is registered as a callback when the background sleep starts, and when the 30 seconds have expired, the operating system reawakens the thread and invokes this method. This method populates the Data field of the AsyncResult object, and then indicates that the operation is now complete. This releases the main thread in the service, waiting in the end method, and allows it to return the data to the client application.

    Notice that the values returned are different if the service performs the operation synchronously (5555555) and asynchronously (9999999).

  7. Rebuild the solution.

Invoke the CalculateTotalValueOfStock operation in the WCF client application

  1. In the AdventureWorksAdminTestClient project, expand the Service References folder, right-click the image from book AdventureWorksAdmin.map file, and then click Update Service Reference.

    This action generates a new version of the client proxy, including the CalculateTotalValueOfStock operation.

  2. Expand the image from book AdventureWorksAdmin.map file, and open the image from book AdventureWorksAdmin.cs file. In the code view window, examine the updated proxy. Notice that the new operation is called CalculateTotalValueOfStock and that there is no sign of the begin and end methods that implement this operation; the fact that this operation is implemented asynchronously is totally transparent to the client application.

  3. Edit the image from book Program.cs file. Remove the statements in the try block that invoke the GenerateDailySalesReport operation and the Console.WriteLine statements, and replace them with the following code shown in bold:

     try {     AdministrativeServiceClient proxy =         new AdministrativeServiceClient("AdventureWorksAdminHttpBinding");              int totalValue = proxy.CalculateTotalValueOfStock("First Calculation");     Console.WriteLine("Total value of stock is {0}", totalValue);     totalValue = proxy.CalculateTotalValueOfStock("Second Calculation");     Console.WriteLine("Total value of stock is {0}", totalValue);     totalValue = proxy.CalculateTotalValueOfStock("Third Calculation");     Console.WriteLine("Total value of stock is {0}", totalValue);     proxy.Close(); }

    These statements simply invoke the CalculateTotalValueOfStock method three times and display the results. Hopefully, the service will execute at least one of these calls in a different manner from the other two (either synchronously or asynchronously).

  4. Start the solution without debugging.

    If you are unlucky, you will have to wait for 20 seconds before you see the first message box appear.

    image from book

    This is because the random number generator in the BeginCalculateTotalValueOfStock method produced an even number and is executing the method synchronously. This should be followed by the following message box:

    image from book

    You will see the result (5555555) displayed in the client application console window as soon as you click OK in the message box.

    If you only see the second message box, the BeginCalculateTotalValueOfStock has decided to execute the method asynchronously. You will then have to wait for up to 30 seconds after closing the message box, until you see the following one:

    image from book

    The value 9999999 should also appear in the client application console window.

  5. Press Enter to close the client application console window.

This is all very well, but so far, we have gone to a lot of trouble to allow the service to determine the best strategy for running a potentially lengthy or expensive operation, although as far as the client application is concerned, everything is still synchronous; each call to the CalculateTotalValueOfStock operation was blocked until it completed. Well, you can also enable asynchronous operations on the client, by regenerating the proxy with the /async flag as mentioned earlier. This is what you will do in the next exercise.

Invoke the CalculateTotalValueOfStock operation asynchronously

  1. Open a Windows SDK CMD Shell window and move to the Microsoft Press\WCF Step By Step\Chapter 11\AdventureWorksAdminTestClient\Service References folder under your \My Documents folder.

  2. In the CMD Shell window, type the following command:

     svcutil http://localhost:9090/AdventureWorksAdmin/Service.svc /namespace:*,AdventureWorksAdminTestClient.AdventureWorksAdmin /async

    This command creates a version of the client proxy that includes asynchronous versions of each of the operations in the service contract.

    Tip 

    If the svcutil command fails with the message “No connection could be made because the target machine actively refused it,” then the ASP.NET Development Web Service has probably shut down. You can restart it by running the solution.

  3. Return to Visual Studio 2005 and examine the image from book AdventureWorksAdmin.cs file under image from book AdventureWorksAdmin.map in the Service References folder.

    Tip 

    If you still have this file open in the code view window from earlier, close the file and reopen it to refresh the display.

    You should see that the client proxy now contains begin and end methods for all of the operations in the service contract (and not just the CalculateTotalValueOfStock operation). Remember that these changes are only implemented in the client proxy and that the service is not actually aware of them.

  4. Edit the image from book Program.cs file in the AdventureWorksAdminTestClient project. Remove the statements that invoke the CalculateTotalValueOfStock operation and the Console.WriteLine statements and replace them with the following code:

     proxy.BeginCalculateTotalValueOfStock("First Calculation",     CalculateTotalValueCallback, proxy); proxy.BeginCalculateTotalValueOfStock("Second Calculation",     CalculateTotalValueCallback, proxy); proxy.BeginCalculateTotalValueOfStock("Third Calculation",     CalculateTotalValueCallback, proxy);

    This code invokes the client-side asynchronous version of the CalculateTotalValueOfStock method three times. The results will be handled by a method called CalculateTotalValueCallback, which you will add next. A reference to the proxy is passed in as the state parameter.

  5. Delete the proxy.Close statement from the try block in the Main method.

    If you close the proxy at this point, the WCF runtime will destroy the channel stack on the client side before the asynchronous calls have completed, and the client application will be unable to obtain the responses from the service.

  6. Add the following method immediately after the end of the Main method in the Program class:

     static void CalculateTotalValueCallback(IAsyncResult asyncResult) {     int total = ((AdministrativeServiceClient)asyncResult.AsyncState).         EndCalculateTotalValueOfStock(asyncResult);     Console.WriteLine("Total value of stock is {0}", total); }

    This is the callback method. When the CalculateTotalValueOfStock operation completes, the proxy will run this method. It retrieves the object passed back from the service (this is the state object, which is a reference to the proxy passed in by the client application as the third parameter in the BeginCalculateTotalValueOfStock method), and uses this object to invoke the EndCalculateTotalValueOfStock method. The value returned by the end method is the calculated total value of the stock from the service.

  7. Start the solution without debugging.

    The client application starts, and then immediately displays the message “Press ENTER to finish.” This is because the calls to the BeginCalculateTotalValueOfStock method are no longer blocking the client application.

    Do not press Enter just yet, but allow the application to continue running. After 20 or 30 seconds, you should see the message boxes that appeared in the previous exercise, indicating whether the service is executing each request synchronously or asynchronously. The results of the calculations should appear in the client console window as the operations complete.

  8. After all three results have been displayed, press Enter to close the client application console window.

From these exercises, you should now fully understand the difference between invoking an operation asynchronously in a client application and implementing an operation that supports asynchronous processing in the service. A developer can decide whether to implement an operation as a pair of methods implementing the IAsyncResult design pattern independently from any client applications. These methods appear as a single operation to the client application, and the implementation is totally transparent. Equally, if a developer creating a WCF client application wishes to invoke operations asynchronously, all she needs to do is generate a proxy using the /async flag with the svcutil utility. Whether the client application invokes an operation synchronously is transparent to the service. You should also realize that although a client application can invoke an operation asynchronously, the service may choose to implement the operation synchronously, and vice versa. The result is complete flexibility on the part of both client applications and services.

There is one final point worth making. You can define synchronous and asynchronous versions of the same operation in a service contract, like this:

 [ServiceContract(…)] public interface IAdventureWorksAdmin {     …     // Synchronous operation     [OperationContract]     int CalculateTotalValueOfStock(string id);     // Asynchronous version     [OperationContract(AsyncPattern = true)]     IAsyncResult BeginCalculateTotalValueOfStock(string id,                                                  AsyncCallback cb,                                                  object s);     int EndCalculateTotalValueOfStock(IAsyncResult r); }

However, if you do this, both operations appear as the same action (CalculateTotalValueOfStock) if you examine the WSDL description of the service. WCF will not throw an exception but will always use the synchronous version of the operation in favor of the asynchronous version (WCF assumes that the synchronous version achieves faster throughput). So, don’t define synchronous and asynchronous versions of the same operation in the same service contract.




Microsoft Windows Communication Foundation Step by Step
Microsoft Windows Communication Foundation Step by Step (Step By Step Developer Series)
ISBN: 0735623368
EAN: 2147483647
Year: 2007
Pages: 105
Authors: John Sharp

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net