Roy Osherove

View Original

Calling webservices asynchronously - 2 possible ways

Calling a web service inside your win forms application is almost trivial. Making the user happy while you do it, is just a little less trivial, but very doable.

Say I have this webservice call (and suppose I have an application that called a webservice on RegexLib.com for that matter..):

If that service contained a webmethod named “ListRegexp(parameters..)”, creating a web reference to that service would create a wrapper class in my project that allowed me to call this webmethod directly, in which case I could write something like this:

private void SearchWeb()

{

 

try

{

      ShowSearchStatus("Querying service...");

 

      com.regexlib.www.Webservices searcher = new com.regexlib.www.Webservices();

      DataSet ds= searcher.listRegExp(GetQueryString());

 

      ShowSearchStatus("Filling results...");

 

      BindGrid(ds);                

      ShowSearchStatus("");

}

catch(Exception e)

{

      ShowSearchStatus(e.Message);

   

}

 

}

This method is called, data is retrieved, statuses are shown, and everything is dandy. Or is it?

 

What if the method takes 1 minute to complete? Remember, this is a web service, and we are helplessly waiting for it to finish its job with no control what so ever over when it finishes. Meanwhile, the user is waiting there. Not only are they waiting there, the GUI is also waiting there, not doing anything, not even repainting itself, or responding to mouse clicks. This is user hell, and your user just joined the party in progress.

So how to we call the web service asynchronously?  two basic ways about it.

 

#1 Web Service Asynchronous invocation

 

If we look carefully at the class that was generated for us when we added the web reference, we can see that for every web method in the webservice, the wrapper class that was generated contains:

  • A method with the same name
  • Begin[Method Name]
  • End[Method Name]

 

So, for our ListRegexp() method, we also have BeginListRegexp and EndListRegexp . these two methods are used if you want to call the webservice asynchronously, without hanging the GUI. In the back, a separate thread is created and a Delegate.BeginInvoke() and EndInvoke() is called. Standard async stuff.

Her’es how to implement async calls using these methods:

private void SearchWebAsync()

{

      //Start the invokation and pass in a callback delegate

      //to be called in finish

      ShowSearchStatus("Querying service...");

 

      com.regexlib.www.Webservices searcher =

new com.regexlib.www.Webservices();

 

      IAsyncResult result =

searcher.BeginlistRegExp(GetQuryString(),

new AsyncCallback(OnSearchFinish),

searcher);

 

}

 

private void OnSearchFinish(IAsyncResult result)

{

            //recieve the searcher that we passed in as state

            //in SearchWebAsync

      com.regexlib.www.Webservices searcher =

(com.regexlib.www.Webservices )result.AsyncState;

            //retrieve the result of the invocation

      DataSet ds= searcher.EndlistRegExp(result);

      BindGrid(ds);

      ShowSearchStatus("");

}

Notice that now I need to implement two methods: one that calls the webservice and invokes the method on it, and one that is called when that webservice method has finished its processing (or on timeout). The first method returns immediately, and our GUI can stay up to date and responsive. The second one is actually passed as a callback to the “Begin[method name]” method of the webservice.

In fact, the Begin[method name} has added two new parameters to the standard web method that was published. The first is a delegate that will be called on method work finish, and second one is state that will be passed into the callback method. This state can be anything you want, or it can be null.

I use it to pass the webservice object itself, so that I can Call “End[Method name]” and get the results I need back. This is only needed if you plan to get results back. If not, you can pass in a null.

As I said, in the callback method I use “End[Method name]” to get back the results that the method returns. Very simple.

You also notice that in the calling method, I receive an IAsyncResult object. I can use this object to control the invocation from the calling end, but this is not needed in this situation.

 

#2: Synchronous invocation, on a separate thread

 

The other way around this: simply use a separate thread to call the intial version of our webSearch method:

private void StartSearchThread()

{

     

      if(_searchThread!=null &&

            _searchThread.ThreadState==ThreadState.Running)            

      {

            _searchThread.Abort();

      }

 

      _searchThread = new Thread(new ThreadStart(SearchWeb));

      _searchThread.Start();

}

What I’m doing here is having a Thread variable in the class that is separately running the synchronous version of my web search method. I can control the thread and stop it when needed, and the code is less complicated to begin with.

One thing to note here that, just like any other multi threaded GUI, you can’t interact with GUI items from a thread that is not the GUI thread. Your application may suffer and hang because of it. So, Here’s how my ShowStatus(string) method is written, to be thread safe:

private delegate void StdDelegateString(string text);

           

private void  ShowSearchStatus(string status)

{

      if(this.InvokeRequired)

      {

            Invoke(new StdDelegateString(ShowSearchStatus),new object[]{status});

            return ;

      }

 

      lblStatus.Text= status;

}

InvokeRequired is a property derived from the Control class. It is one of the few that are thread safe. It returns through if the thread that is asking it is not the GUI thread. Control.Invoke is the other Thread Safe method we need here. It allows us to call any method using the GUI thread. Here, our method is calling itself with the same parameters if it finds out it is not being called from the GUI thread. Notice the clumsiness of this approach. I have to define my own delegate so that I can call a method that receives a string as a parameter.

Currently, this is the most straight forward way of doing Thread safe GUI operations. Hopefully, this is fixed in .Net 2.0