Reader Level:
ARTICLE

Multi-threaded Asynchronous Programming in C#... Through The Web Service. Part V

Posted by Matthew Cochran Articles | Multithreading in C# May 27, 2007
In my last article I discussed a general approach to building an async architecture. In this article we’ll look at extending this basic architecture and incorporating async web services.
  • 0
  • 0
  • 52175
Download Files:
 

In my last article I discussed a general approach to building an async architecture.  In this article we'll look at extending this basic architecture and incorporating async web services.

Part I. Getting Async'd

Since my last article, Richter has written a great article in implementing the CLR async model.  We'll be incorporating two of his classes implementing IAsyncResult from his article AsyncNorResult and AsyncResult<> and using them to implement an async web service.

    public interface IAsyncResult
   {
        object AsyncState { get; }
        WaitHandle AsyncWaitHandle { get; }
        bool CompletedSynchronously { get; }
        bool IsCompleted { get; }
}

In a bit, we'll look at building a async web service with a long running task using the pattern presented in the Richter article but first we'll create an object with a synchronous method and look at how we can provide asynchronous hooks into an existing method.

Here is our class with our fake long running process:

internal class SampleBase
{
    public Int32 DoSomething()
    {
        Thread.Sleep(150); // DON'T DO THIS: USED TO FAKE A LONG RUNNING PROCESS
        Random rand = new Random();
        return rand.Next();
    }
}

To get started implementing this method asynchronously, we'll inherit from this class. We can provide the synchronous and asynchronous method calls in the same class definition, I just chose to separate them out here for clarity.

internal class Sample: SampleBase{}

Our end goal is to provide async hooks into this method using  the standard CLR nomenclature of Begin... and End..., but before we do that we need a helper method that will give us a method that we'll call asynchronously through a WaitCallback delegate.

internal class Sample: SampleBase
{       
    private void DoSomethingHelper(Object asyncResult)
    {
        AsyncResult<Int32> result = asyncResult as AsyncResult<Int32>;

        try
        {
            Int32 value = DoSomething();
            result.SetAsCompleted(false, value);
        }

        catch (Exception ex)
        {
            result.SetAsCompleted(false, ex);
        }
    }
}

Our DoSomethingHelper() method needs a signature of a WaitCallback because it we'll be manually adding it to the threadpool using ThreadPool.QueueUserWorkItem().  Richter's implementation is straight forward and if you want to dig in, I encourage you to read his article to understand the internals of his AsyncResult<> class.

Next we'll create our BeginDoSomething() method and EndDoSomething() methods.  The BeginDoSomething() method is used to send our request as in my previous article.  The EndDoSomething() method is a courtesy we're providing to others implementing our asyc call to get the result value out.

public IAsyncResult BeginDoSomething(AsyncCallback callback, Object state)
{
    AsyncResult<Int32> result = new AsyncResult<int>(callback, state);
    ThreadPool.QueueUserWorkItem(DoSomethingHelper, result);
    return result;
}

public Int32 EndDoSomething(IAsyncResult asyncResult)
{
    AsyncResult<Int32> result = asyncResult as AsyncResult<Int32>;
    return result.EndInvoke();          
}

So now we have an asynchronous way to call our synchronous long running DoSomething() method.  There may be a situation where we need to wrap the async call and can easily do so as in the following class:

class SampleWrapper
{
    public SampleWrapper()
    {
        samp = new Sample();
    }
    private Sample samp;

    public IAsyncResult BeginDoSomething(AsyncCallback callback, Object state)
    {
        return samp.BeginDoSomething(callback, state);
    }

    public Int32 EndDoSomething(IAsyncResult asyncResult)
    {
        return samp.EndDoSomething(asyncResult);
    }
}

Part II. Implementation for non-web service.

Now that we have async hooks into our synchronous method, let's look at wiring it up.  There are a couple ways of going about this.  The first way we'll look at is by providing a callback method we can set up in the main call as follows:

/// <summary>
/// Demonstrates a async call using a callback method
/// </summary>
public static void Sample1()
{
    Sample samp = new Sample();
    samp.BeginDoSomething(ShowResult, samp);
}

/// <summary>
/// The callback method
/// </summary>
/// <param name="result">The result.</param>
static void ShowResult(IAsyncResult result)
{
    Sample samp = result.AsyncState as Sample;
    Int32 value = samp.EndDoSomething(result);
    Console.WriteLine("value: " + value);
}         

The alternative (which I think is a bit slicker) way is to do the same thing with an anonymous delegate as follows:

/// <summary>
/// Demonstrates a async call using a anonymous method
/// </summary>
public static void Sample2()
{
    Sample samp = new Sample();
    samp.BeginDoSomething(delegate(IAsyncResult result)
    {

        Int32 value = samp.EndDoSomething(result);
        Console.WriteLine("value: " + value);
    }, null);
}

Part III. The Web Service.

If you've followed along so far than the next part should be a piece of cake.  In implementing the async web service, we'll use the same pattern as earlier, but just mark the Begin...() and End...() methods with the [WebMethod] attribute, and the rest is handled for us.  It is then up to the client as to how they would like to call the service.

[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class GetIntService : System.Web.Services.WebService
{
    public GetIntService() { }

    [WebMethod]
    public IAsyncResult BeginGetInt(AsyncCallback callback, Object state)
    {
        AsyncResult<Int32> result = new AsyncResult<int>(callback, state);
        ThreadPool.QueueUserWorkItem(GetRandomIntHelper, result);
        return result;
    }

    [WebMethod]
    public Int32 EndGetInt(IAsyncResult result)
    {
        AsyncResult<Int32> r = result as AsyncResult<Int32>;
        return r.EndInvoke();
    }

    private static void GetRandomIntHelper(Object asyncResult)
    {
        AsyncResult<Int32> result = asyncResult as AsyncResult<Int32>;

        try
        {

            Int32 value = GetRandomInt();
            result.SetAsCompleted(false, value);
        }

        catch (Exception ex)
        {
            result.SetAsCompleted(false, ex);
        }
    }

    private static Int32 GetRandomInt()
    {
        Thread.Sleep(500);
        Random rand = new Random();
        return rand.Next();
    }
}

The cool thing is that we've used the same pattern for implementing async method calls and async web method calls and the web service is built with stubs to call the method without the Begin... and End... prefixes.

Part IV. Service Consumption.

To consume our async web service we'll wire up an event handler method so when the service returns, the event is raised and we get our result:

public static void Sample5()
{
    Svc.GetIntService service = new Svc.GetIntService();
    service.GetIntCompleted += Sample5Response;
    service.GetIntAsync();
}

public static void Sample5Response(object sender, Svc.GetIntCompletedEventArgs args)
{
    Console.WriteLine("result: " + args.Result);        
}

And that's about it. Pretty easy and very cool. The source code for this article also demonstrates the async method call wrapper.  Next time I'll look into implementing a long running i/o bound task with through an async web service.

If you are interested in async pages, http handlers and modules, check out this article


Until next time,
Happy coding

COMMENT USING

Trending up