Using asynchronous methods

The client-side asynchronous capabilities of a web service are defined by the underlying development environment, in this case, .NET. The WSDL toolkit (wsdl.exe) included with the .NET SDK generates two types of asynchronous methods: Begin\End and Async\Completed. Both are completely valid and supported in .NET 2.0, 3.0, 3.5, 4.0, and 4.5. Note, using "Add Web Reference" within Visual Studio 2005 will only generate the Async\Completed methods. To construct both, use wsdl.exe on the command line.

The Begin\End pattern has existed since .NET 1.1 and uses a callback technique for managing and tracking an asynchronous call. The Async\Completed pattern was introduced in .NET 2.0 as an event-driven asynchronous model. Both techniques initiate a call to the remote web service on a threadpool thread. The asynchronous response to the callback method for the Begin\End pattern is handled in a threadpool thread. The difference lies in how the response is handled.

The response handled by Begin's callback method executes on a threadpool thread and is provided with an IAsyncResult type. In the callback method, you must explicitly call the End method and provide the IAsyncResult type. This means you must have a reference the original proxy type on which to call the method.

The Completed handler executes on the main thread, so managing UI updates to a Windows form with results from an asynchronous web service call does not require that the developer manage cross thread interaction. The event to a Completed handler contains an IAsyncResult and the object returned from the method call (so you don't need a reference to the original proxy type).

Note, both patterns include access to an IAsyncResult which maintains an IsCompleted property and can be used to check the status of the asynchronous request. In many cases, the event-driven model may be easier to implement.

Both patterns are illustrated below via a set of button click events in a Windows form and a separate custom IAsyncResult class. In general, these methods were designed for use within desktop clients, but you can use them in a web application. In that case you'll need to explicitly manage synchronicity between the client-web and web-service. In most cases you'll likely need to spawn a separate thread to use the asynchronous methods.

Form.cs (partial page)

private void button1_Click(object sender, EventArgs e)

{

      MapService_MapServer mapservice = new MapService_MapServer();
      mapservice.Url = "http://localhost:6080/arcgis/services/MapService/MapServer";

 
      // <Code missing> Construct MapDescription (mapdesc) and ImageDescription (imgdesc)

 
      mapservice.ExportMapImageCompleted += new ExportMapImageCompletedEventHandler(mapservice_ExportMapImageCompleted);
      // Simple execution on main thread


      mapservice.ExportMapImageAsync(mapdesc, imgdesc);
      // Triggers request on threadpool thread

}

// Event handled on main thread
void map_ExportMapImageCompleted(object sender, ExportMapImageCompletedEventArgs e)

{            
      MapImage mapimg = e.Result;
      // Keep Result in Session or other storage, retrieve via another app call.
}

private void button2_Click(object sender, EventArgs e)

{
      MapService_MapServer mapservice = new MapService_MapServer();

      mapservice.Url = "http://localhost:6080/arcgis/services/MapService/MapServer";

    
      // <Code missing> Construct MapDescription (mapdesc) and ImageDescription (imgdesc)

      // Create unique guid as job id for async call
      string guid = Guid.NewGuid().ToString();

 
      // Create custom IAsyncResult to store custom properties (map server proxy, job id, etc.)
      AsyncDemo.AsyncResult asyncResult = new AsyncDemo.AsyncResult(mapservice, guid, null, null);

 
      // Simple execution on main thread
      mapservice.BeginExportMapImage(mapdesc, imgdesc, new AsyncCallback(CallbackMethod), asyncResult);            

}

// Method called on threadpool thread
private void CallbackMethod(IAsyncResult result)
{
      AsyncDemo.AsyncResult asyncResult = (AsyncDemo.AsyncResult)result.AsyncState;
      ESRI.ArcGIS.SOAP.MapServerProxy mapservice =
      (ESRI.ArcGIS.SOAP.MapServerProxy)asyncResult.ServerProxyInstance;

 
      ESRI.ArcGIS.SOAP.MapImage mapimg = mapservice.EndExportMapImage(result);

 
      // Keep result in Session or other storage, retrieve via another app call.

}

AsyncDemo.AsyncResult.cs

using System;

using System.Collections.Generic;

using System.Text;

using System.Threading;

 

namespace AsyncDemo

{

      public class AsyncResult : IAsyncResult, IDisposable

      {

            System.Web.Services.Protocols.SoapHttpClientProtocol serverProxy;

            string jobID;

            AsyncCallback callback;

            object state;

            ManualResetEvent manualResentEvent;

 
            public AsyncResult(System.Web.Services.Protocols.SoapHttpClientProtocol serverProxy, string jobID, AsyncCallback callback, object state)

            {

                  this.serverProxy = serverProxy;

                  this.jobID = jobID;

                  this.callback = callback;

                  this.state = state;

                  this.manualResentEvent = new ManualResetEvent(false);

            }

 
            public AsyncResult(AsyncCallback callback, object state)

            {

                  this.callback = callback;

                  this.state = state;

                  this.manualResentEvent = new ManualResetEvent(false);

            }

 
            public System.Web.Services.Protocols.SoapHttpClientProtocol ServerProxyInstance

            {

                  get { return serverProxy; }

            }

 
            public string JobID

            {

                  get { return jobID; }

            }

 
            object IAsyncResult.AsyncState

            {

                  get { return state; }

            }

 
            public ManualResetEvent AsyncWait

            {

                  get

                  {

                        return manualResentEvent;

                  }

            }

 
            WaitHandle IAsyncResult.AsyncWaitHandle

            {

                  get { return this.AsyncWait; }

            }

 
            bool IAsyncResult.CompletedSynchronously

            {

                  get { return false; }

            }

 
            bool IAsyncResult.IsCompleted

            {

                  get { return manualResentEvent.WaitOne(0, false); }

            }


            public void Complete()

            {

                  manualResentEvent.Set();

                  if (callback != null)

                        callback(this);

            }


            public void Dispose()

            {

                  manualResentEvent.Close();

                  manualResentEvent = null;

                  state = null;

                  callback = null;

            }

      }

}

11/8/2016