October 23, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

Calling a Web Service Asynchronously

  • September 17, 2002
  • By Kate Gregory
  • Send Email »
  • More Articles »

From Kate Gregory's Codeguru column, "Using Visual C++ .NET".

Web services are a terrific way to integrate disparate systems: cross-platforms, cross-programming-languages, cross-everything. No matter what language you work in, you should be able to do an HTTP GET and parse a little XML. As I showed in an earlier column, it's incredibly simple if you're using Visual C++ to both create and consume Web Services.

But to drill a little deeper into the problem, you mustn't forget that a Web service is accessed over the network. What if the network is slow? Or what if the Web service has to root around in a database and do all kinds of calculations before returning an answer? It can take quite a while to get a return value from the Web service, and you don't want your application to be blocked while you're waiting for that answer.

Luckily the code that's generated for you when you mark a function with <WebMethod> goes beyond SOAP wrappers and HTTP listeners: you also get an asynchronous version of your web method, with no extra coding. This is an extension of a pattern set up throughout the Base Class Library (BCL) of the .NET Framework: potentially slow functions have a companion Begin method that calls the function asynchronously, so that your code is not blocked. The Begin method returns immediately and when the slow function is finished, a function you write (known as a callback) is called to notify your code that the process is complete. Your callback calls a companion End method that returns the value calculated for you.

A slow web service

I started with a copy of the Calculator project I used in my previous column, and added another Web method called SlowAdd(). I added the declaration of it to Calculator.h:

[System::Web::Services::WebMethod] 
double  SlowAdd(double x, double y);

Then the implementation to Calculator.cpp:

double CalculatorService::SlowAdd(double x, double y)
{
    System::Threading::Thread::Sleep(1000); // 1 second
    return x + y;
}

This does the same job as Add(), but adds a one-second pause to imitate a slow connection or a slow calculation by the web service. Notice that I don't have to add any code to make this service available asynchronously.

Calling the web service

If you want to start with a copy of the CalcTest project I used in my previous column, you'll need to update the WSDL that Visual Studio uses to create a proxy for the web service. In Solution Explorer, right-click Calculator.wsdl and choose Update Web Reference. If you're starting from scratch, create a Managed C++ application, add a web reference to the Calculator web service, and you're ready to go.

The main function starts out simply enough. Here's a version that only makes the synchronous call, as you saw last time:

int _tmain(void)
{
  CalculatorService * Calc = new CalculatorService;
  System::Console::WriteLine("1 plus 1 synchronously is {0}",
                             __box(Calc->Add(1,1)));
    return 0;
}

Now for the asynchronous work. If I were to call SlowAdd(), it would be a synchronous call. To make an asynchronous call, I call BeginSlowAdd() instead, which was generated for me when I marked SlowAdd() as a Web method. The Begin methods need to be passed a callback object to indicate where the notification will go when the slow method has completed. That's what frees your application to go on and do something more excisting instead of being blocked waiting for the web method to return.

You'll find lots of Visaul Basic and C# examples of creating, but C++ examples are a little thin on the ground, and that's a shame because it is a bit tricky. Instead of just passing the address of the function to the delegate constructor, you need to pass two parameters: a pointer to an object instance and a function pointer to the member function. Well here's problem number one: this is a console application, and it's object-free at the moment. That means you have to write an object whose only purpose in life is to hold this member function. It needs to be a managed object, so you'll need the __gc qualifier on the class definition. I added this code before _tmain():

__gc class CallBack 
{
public:
  void AddCallback(System::IAsyncResult* ar);
};

The signature of this callback method is pre-established for you. When you call a Begin method you must pass it not just any old function address, but the address of a function with this signature: it takes an IAsyncResult pointer and returns void. I'll show you the code for this function shortly, but in the meantime here are the lines I added to _tmain():

System::Console::WriteLine("adding 2 plus 2 asynchronously");
CallBack* callbackobject = new CallBack();
System::AsyncCallback* callback = 
          new System::AsyncCallback(callbackobject, 
                                    &CallBack::AddCallback);
Calc->BeginSlowAdd(2,2,callback,Calc);
for (int i= 0; i<20; i++)
{
  System::Console::WriteLine(".");
  System::Threading::Thread::Sleep(100); // 0.1 second
}

This code creates an instance of the class and makes an AsyncCallback from that instance. It then calls BeginSlowAdd(), passing in the two parameters that SlowAdd() expects, the callback, and the web service proxy itself - this last parameter will be given to the callback, as you'll see shortly. After the call to BeginSlowAdd(), just to prove that the application can do something else while the slow Web method proceeds, the code goes on to sleep for a tenth of a second at a time, and print out dots. This lets you know it's working.

The callback itself is a member function of the CallBack() class I created. The implementation looks like this:

void CallBack::AddCallback(System::IAsyncResult* ar)
{
  CalculatorService * Calc = (CalculatorService*) ar->AsyncState;
  double answer = Calc->EndAdd(ar);
  System::Console::WriteLine("result is {0}", __box(answer));
}

The first thing this code does is to get the AsyncState pointer out of the IAsyncResult that was passed to the callback and cast it back to a CalculatorService pointer. You know that's what it is, because that's what was passed as the last parameter to BeginSlowAdd(). I am using an old-fashioned C-style cast here for readability: it gets me a warning that I should use the static_cast template instead.

Once this code has a CalculatorService pointer, it uses it to call EndAdd(), which returns the answer to the original question (2+2, in case you've forgotten). It then boxes the answer and passes it to Console::WriteLine().

When this code runs, the exact delay before the answer appears will vary, but generally you will see output like this:

1 plus 1 synchronously is 2
adding 2 plus 2 asynchronously
.
.
.
.
.
.
.
.
.
.
.
result is 4
.
.
.
.
.
.
.
.
.

You can see that the result from the slow Web service just pops in among the regular stream of dots from the loop in _tmain(). This demonstrates that the Web service is being called asynchronously. There is no effort for a Web service developer to offer asynchronous versions of all the Web methods in the Web service at all, and little effort required to call a Web service asynchronously. It's worth looking into!

About the Author

Kate Gregory is a founding partner of Gregory Consulting Limited (www.gregcons.com). In January 2002, she was appointed MSDN Regional Director for Toronto, Canada. Her experience with C++ stretches back to before Visual C++ existed. She is a well-known speaker and lecturer at colleges and Microsoft events on subjects such as .NET, Visual Studio, XML, UML, C++, Java, and the Internet. Kate and her colleagues at Gregory Consulting specialize in combining software develoment with Web site development to create active sites. They build quality custom and off-the-shelf software components for Web pages and other applications. Kate is the author of numerous books for Que, including Special Edition Using Visual C++ .NET.

# # #






Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Sitemap | Contact Us

Rocket Fuel