Click here to Skip to main content
15,881,559 members
Articles / Programming Languages / C#
Article

Asynchronous design patterns.

Rate me:
Please Sign up or sign in to vote.
4.86/5 (32 votes)
24 Apr 200719 min read 116.8K   928   171   3
This article describes the asynchronous design pattern, it's implementation, it's limitations and ways to improve the situation in certain circumstances.

Sample Image - async_pattern.png

Introduction

NB: I removed comments and some details from the code for this article for brevity. To see the full code with comments download the sample project.

In .NET, Microsoft had introduced so-called asynchronous design pattern.

Suppose we have a method named LingeringCall that might execute for rather long time. "Rather long" usually means that the end user notices the interface of the program freezes during that call. Of course, you, as the author of the program, may execute this method on another thread and pass the results somehow to the UI. To conserve resources Microsoft had even introduced the ThreadPool class that ideally suited for executing a multitude of such "rather long" operations. But the last part of picture, retrieving the results in the context of the main thread, might become complex. Thus the asynchronous pattern comes to play.

The pattern consists of two additional methods for an asynchronous operation, an object that implements the IAsyncCallback interface and a callback delegate. According to Microsoft's specification you should split the execution of the asynchronous method in two steps. First step prepares the background operation and commences it. Second step waits for the background operation competition and performs necessary clean up. If we write an asynchronous version of the LingeringCall we introduce two new methods. The first method is called BeginLingeringCall, it has all the parameters of the original LingeringCall plus two extra: a callback delegate of type AsyncCallback and a state object. The BeginLingeringCall always returns the IAsyncResult object. The second method, obviously called EndLingeringCall, always has at least single parameter of type IAsyncResult and returns the LingeringCall's result, if any. More on organizing these methods you find in the SDK documentation.

The most important piece of the puzzle is the IAsyncResult object. It contains following properties:

  • AsyncState - it's the external state object passed to the BeignLingeringCall call as the last parameter; you'll see the practical use of it later.
  • AsyncWaitHandler - it's the object that synchronizes access to the results of the pending operation.
  • CompletedSynchronously - this flag indicated if the LingeringCall call completed synchronously or not; sometimes the BeignLingeringCall might decide that the operation will be performed fast enough to be executed on the calling thread.
  • IsCompleted - this flag indicates if the asynchronous operation completed or not.

As you can see, the IAsyncResult provides just enough information for the caller to poll the state of the operation. Suppose we have called LingeringCall and got an instance of the IAsyncResult object. To query if the operation still continues, we may use code:

C#
if(!asyncResult.IsCompleted)

or

C#
if(!asyncResult.AsyncWaitHandle.WaitOne(0, false))

You may safely assume that both ways are pretty much the same.

If you just want to wait until the operation is completed you should use code like this:

C#
asyncResult.AsyncWaitHandle.WaitOne();

Keep in mind that you should not use a loop that constantly queries the IsCompleted property. You actually can, if you don't care about efficiency.

Another good thing is that if you perform a lot of similar asynchronous operations at once you may create an array of WaitHandle objects, fill it with values of AsyncWaitHandle properties of all IAsyncResult instances and use WaitHandle.WaitAll or WaitHandle.WaitAny static methods to poll their states at once.

The next piece of puzzle is the AsyncCallback delegate passed to the BeginLingeringCall. This delegate accepts a single parameter of type IAsyncResult and does not return a value. The method the delegate points at gets called when the asynchronous operation is completed. If you don't want or don't need to poll the operation's status, you should use this callback delegate. But remember, always call the EndLingeringCall- it is required to finish the operation and release all the resources associated with it. Keep in mind that the callback usually gets called in the context of another thread so you must synchronize access to shared data. And avoid exceptions to propagate outside of this method or you'll introduce nasty, hard to trace errors.

Now you see that IAsyncResult/AsyncCallback pair gives us a great deal of flexibility. You can poll the operation's status or you may receive a notification when the operation is completed. The latter suits methods that do not return results.

The .NET class library provides us with many examples of asynchronous operations, the most notable is asynchronous delegates pattern that makes possible to call any method asynchronously. Most implementations of the pattern use ThreadPool to perform the BeginXXXX in a background thread but some classes, like FileStream or Socket use much more effective overlapped I/O API, at least, in Windows. Anyway, from the caller's point of view we see the usual picture: the LingeringCall that executes synchronously, the BeginLingeringCall/EndLingeringCall pair and the IAsyncResult/AsyncCallback pair.

Implementing the pattern

I distinguish two ways to implement the pattern. The first method is straightforward: the BeginLingeringCall spawns a thread or a thread pool's work item that executes the LingeringCall, the EndLingeringCall uses a custom implementation of the IAsyncResult to wait for the LingeringCall's completion. The second method is a bit more complex but very useful if your LingeringCall uses classes and methods that already implement the pattern.

Executing a method on the background thread

Let's code. Because my imagination is very limited I can't think of a useful example so I demonstrate the pattern with an oversimplified calculation task.

Take a look at the AsyncMethod sample, my Fixture class and it's LingeringCall method. The name Fixture came from the NUnit framework (this class is a kind of a test fixture). The LingeringCall just accumulates some data in a loop. If you set the number of iteration to a big value, the LingeringCall would take couple of seconds to complete.

To turn the LingeringCall into an asynchronous method we should perform following steps:

  1. Write an implementation of IAsyncResult interface. Because this implementation is going to be reused, the code would look like:
    C#
    public class AsyncResult: IDisposable, IAsyncResult
    {
        AsyncCallback callback;
    
        object state;
    
        ManualResetEvent waitHandle;
    
        public AsyncResult(AsyncCallback callback, object state)
        {
            this.callback = callback;
            this.state = state;
            this.waitHandle = new ManualResetEvent(false);
        }
    
        public void Complete()
        {
            try
            {
                waitHandle.Set();
                if(null != callback)
                    callback(this);
            }
            catch
            {}
        }
    
        public void Dispose()
        {
            if(null != waitHandle)
            {
                waitHandle.Close();
                waitHandle = null;
                state = null;
                callback = null;
            }
        }
    
        public void Validate()
        {
            if(null == waitHandle)
                throw new InvalidOperationException();
        }
    
        public object AsyncState
        {
            get
            {
                return state;
            }
        }
    
        public ManualResetEvent AsyncWaitHandle
        {
            get
            {
                return waitHandle;
            }
        }
    
        WaitHandle IAsyncResult.AsyncWaitHandle
        {
            get
            {
                return this.AsyncWaitHandle;
            }
        }
    
        public bool CompletedSynchronously
        {
            get
            {
                return false;
            }
        }
    
        public bool IsCompleted
        {
            get
            {
                return waitHandle.WaitOne(0, false);
            }
        }
    }

    Note that usually the methods like Validate throw the ObjectDisposedException, not the InvalidOperationException, but this is the requirement of the pattern, the Validate called in our EndXXXX methods.

  2. Add to the Fixture a nested private class derived from the AsyncResult with four additional fields: one for the result of the call and three for each LingeringCall parameter:
    C#
    private class AsyncResult: Pvax.Utils.AsyncResult
    {
        internal AsyncResult(double seed, double delta, int iterations, 
            AsyncCallback callback, object state):
            base(callback, state)
        {
            this.Seed = seed;
            this.Delta = delta;
            this.Iterations = iterations;
        }
    
        internal double Result;
    
        internal readonly double Seed;
    
        internal readonly double Delta;
    
        internal readonly int Iterations;
    }

    I don't bother writing access properties and just make the very fields accessible. This class is private and I consider it "the implementation detail" so I don't need to OOP in my purists here. The readonly modifiers here are to indicate that the parameters are in parameters and help me to trace errors in other parts of the code. If the LingeringCall had in/out or out parameters, I'd omit the modifier for these.

  3. Add to the Fixture class a private method named Run:
    C#
    void Run(object state)
    {
        AsyncResult asyncResult = state as AsyncResult;
        if(null == asyncResult)
            return;
        try
        {
            asyncResult.Result = LingeringCall(asyncResult.Seed, 
                asyncResult.Delta, asyncResult.Iterations);
        }
        finally
        {
            asyncResult.Complete();
        }
    }

    This method is actually a work item for the ThreadPool class. I pass an instance of my private AsyncResult class as the state object. Note the Complete call at the end of the method and it's implementation. At this point we set the AsyncWaitHandle to signaled state and invoke the callback delegate.

  4. Add to the Fixture class a public method named BeginLingeringCall:
    C#
    public IAsyncResult BeginLingeringCall(double seed, double delta, 
        int iterations, AsyncCallback callback, object state)
    {
        AsyncResult asyncResult = new AsyncResult(seed, delta, iterations, 
            callback, state);
        ThreadPool.QueueUserWorkItem(new WaitCallback(Run), asyncResult);
        return asyncResult;
    }
  5. Add to the Fixture class a public method named EndLingeringCall:
    C#
    public double EndLingeringCall(IAsyncResult ar)
    {
        if(null == ar)
            throw new ArgumentNullException("ar");
        AsyncResult asyncResult = ar as AsyncResult;
        if(null == asyncResult)
            throw new ArgumentException("", "ar");
        asyncResult.Validate();
        asyncResult.AsyncWaitHandle.WaitOne();
        double res = asyncResult.Result;
        asyncResult.Dispose();
        return res;
    } 

That is actually it, nothing too complex.

Let me summarize the rules of implementing the asynchronous pattern for a method:

  1. Create a (private and nested) state class, derived from AsyncResult or, if the method you are converting has no parameters and returns no value, use it as is.
  2. Create a (private) method, it's signature should be compatible with WaitCallback delegate; this method calls the original, but on the background thread.
  3. Write a BeginXXXX method; it should pack the parameters to the state object and call ThreadPool.QueueUserWorkItem static method.
  4. Write an EndXXXX method; it should wait for the background operation completion and return the result of it.

Issues

From this sample you can see some drawbacks of the asynchronous design pattern. Let's review them.

Synchronization

Although Microsoft states it in the documentation, I repeat it once more: the callback passed to a BeginXXXX method usually gets called on another thread. Thus you must synchronize access to any shared data.

There's a trick that helps you to avoid expensive synchronization, I learned it from the weblog of Mike Taulty. He introduces a queue of AsyncCallback delegates that get invoked in the application idle time. See more on this here.

Ability to cancel the pending operation

To my knowledge, no built-in classes allow you to cancel a pending operation. For instance, if you're sending a buffer with a Socket, you have to close the socket, you can't event set a timeout for it if you're using the asynchronous sockets. A timeout can be implemented with ThreadPool.RegisterWaitForSingleObject static method, but there is no explicit cancel behaviour.

Re-entrance

If you look at my AsyncMethod sample you'll see that it is safe to call the BeginLingeringCall before the previous asynchronous operation is complete. It is because the worker method does neither modify the Fixture's instance data nor depends on any critical or unmanaged resources. Obviously, it is not always the case. If the method you're turning to an asynchronous one is not re-entrant, just keep a reference to an IAsyncResult instance until the EndXXXX method where you set it to null, in the BeginXXXX method check the reference for null and if it is not, throw an InvalidOperationException. You'll see an example of a non-re-entrant asynchronous method in the sample code.

Split cleanup

The asynchronous design pattern easily leads to hard to trace bugs if you forget to call EndXXXX methods. It is a well-known, or should I say, infamous bug pattern named split cleaner. You acquire an unmanaged resource in one place and forget to release it in another. More on this bug pattern you can find here. The article mentions Java but also suites for .NET.

All I can do is to repeat: don't forget to call EndXXXX methods. Put them in finally clauses, use callbacks or Taulty's AsyncIdleQueue, but call them.

Multicasts

As you know, all real delegates in .NET are multicasts. It means that a delegate invocation may end up as a series of methods calls. It is perfect for events so many objects can subscribe for a single event and a disaster for callbacks. So always check the callback delegates for being singlecasts:

C#
if(null != callback)
  if(callback.GetInvocationList().Length > 1)
     throw new InvalidOperationException("A callback must not be multicast.");

Exception propagation

It is unclear to me what happens in the BCL code if an exception occurs during the execution of an asynchronous method. It seems to me that the background operation just fails. However, it is clear that we can propagate the exception to the caller by adding a field of type Exception to the AsyncResult descendant, catching the exception in the Run method, assigning it to the field and throwing the exception in the body of the EndXXXX method to notify the caller about the error and it's cause.

Wrapping asynchronous calls chains

Using the ThreadPool for asynchronous methods might be not a good idea. Suppose we are writing an application that serves hundreds and thousands client request per second. If each asynchronous call takes seconds to complete the queue of work items to be served might become enormous.

To help with the issue we should use a smarter approach. First, let's remember overlapped I/O. It is not very clear to me how it works, but from MSDN I gather that some services like file I/O and sockets queue requests for reading and writing and perform them at once in the context of a kernel thread. Obviously, it is faster and conserves system resources. Second, we can split long running tasks into atomic pieces and perform them step by step thus minimizing load on the thread pool. In this case you chain I/O calls using BeginXXXX and AsyncCallback and set your own AsyncWaitHandle to signaled state when all pieces are executed. This method of programming complex operations is sometimes called continuation.

Let's write a sample program that downloads a file from Web servers using my favorite guinea pig, the hypertext transfer protocol (HTTP). Of course, the operation will be performed asynchronously.

HTTP abstracts

The truth is, HTTP protocol is very simple so I won't use the standard System.Net.HttpWebRequest and other high level classes but write my own instead. You may find many descriptions of the protocol on the Net like this one. I'll just explain here the basics and tell about difficulties we are going to face.

A download of a single Web page in your browser's window consists of one or more download sessions, depending on the page's complexity. Each session downloads a single file or, strictly speaking, a resource. In HTTP 1.0 each session opens a connection to the server, in HTTP 1.1 many sessions may share a single connection.

A session begins with a request. The request is a bunch of text lines. The first line always specifies the HTTP protocol method, the resource and the version of the protocol. The last line is empty. The rest of lines contain co-called HTTP headers, key-value pairs that describe optional parameters of the request. There are a lot of methods in HTTP protocol, but we are interested in the GET method that downloads a file from the server.

When the server receives the request, it sends back a response. The response consists of a header and a body. The response header, just like the request, is a bunch of text lines. The first line is the response success code, the last line is empty and the rest of the lines are also HTTP headers. The request body contains the data and can be either text or binary.

HTTP 1.0 sends the response body in one piece, but HTTP 1.1 may split the data into chunks. Each chunk consists of a text line with the chunk length as a hexadecimal number and followed by the data. The last chunk has the length of zero and the whole data is followed by an empty line.

HTTP GET implementation, common bits

Because the protocol sometimes treats the bytes being sent as characters, it is very hard to use .NET-provided high-level I/O classes as TextReader to parse the response. To simplify the client a bit I created a simple finite state machine that parses the response byte by byte. Depending on state, I sometimes treat bytes as 8-bit characters which probably is not quite correct because I hear some HTTP headers use UTF-8 encoding, but the two headers I really need don't.

All the processing is done in HttpResponseParser class that exposes beside it's constructor a single method, ProcessByte. To process headers and response data I wrote a callback interface named IHttpResponseHandler. Keep in mind that the code is not 100% robust because the rules how to form and parse HTTP requests and responses are very lax. I leave the source code in the sample project because they do not relate to the main theme of the article.

HTTP GET implementation, take one

This implementation uses the asynchronous design pattern. The Getter class has three public methods: Get, BeginGet and EndGet (implementations omitted):

C#
public class Getter: IHttpResponseHandler
{
    public void Get(Uri uri, Stream output);

    public IAsyncResult BeginGet(Uri uri, Stream output, 
        AsyncCallback callback, object state);

    public void EndGet(IAsyncResult ar);
} 

The funny thing is that the implementation of the Get method consists of a single line:

C#
public void Get(Uri uri, Stream output)
{
    EndGet(BeginGet(uri, output, null, null));
} 

Now let's implement the operation step by step. Resolve the name of the server:

C#
public IAsyncResult BeginGet(Uri uri, Stream output, AsyncCallback callback, 
    object state)
{
    this.uri = uri;
    this.output = output;
    this.socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, 
        ProtocolType.Tcp);
    this.asyncResult = new AsyncResult(callback, state);
    this.recvBuffer = new byte[256];

    Dns.BeginGetHostByName(uri.Host, new AsyncCallback(GetHostByNameDone), 
        null);
    return asyncResult;
}

Connect to the server:

C#
void GetHostByNameDone(IAsyncResult ar)
{
    try
    {
        IPHostEntry ihe = Dns.EndGetHostByName(ar);
        IPAddress address = ihe.AddressList[0];
        IPEndPoint trackerEndpoint = new IPEndPoint(address, uri.Port);
        socket.BeginConnect(trackerEndpoint, new AsyncCallback(ConnectDone), 
            null);
    }
    catch(Exception)
    {
        Complete();
    }
}

Send the request:

C#
void ConnectDone(IAsyncResult ar)
{
    try
    {
        socket.EndConnect(ar);
        MemoryStream output = new MemoryStream();
        StreamWriter writer = new StreamWriter(output, Encoding.ASCII);
        writer.Write("GET {0} HTTP/1.0\r\nHost: {0}\r\nUser-Agent: 
            AsyncHttpGet1\r\nConnection: close\r\n\r\n", uri, uri.Host);
        writer.Flush();
        byte[] toSend = output.ToArray();
        socket.BeginSend(toSend, 0, toSend.Length, SocketFlags.None, 
            new AsyncCallback(SendDone), null);
    }
    catch(Exception)
    {
        Complete();
    }
}

I send a bare minimum of the HTTP headers. The Host is required by the protocol, the User-Agent helps the Web site administrator to parse the server logs, and the Connection header notifies the server that we are not going to use "kept alive" connection and it can close it right after sending the response.

Receive the response:

C#
void SendDone(IAsyncResult ar)
{
    try
    {
        socket.EndSend(ar);
        socket.Shutdown(SocketShutdown.Send);
        Receive(new HttpResponseParser(this));
    }
    catch(Exception)
    {
        Complete();
    }
}

void Receive(HttpResponseParser parser)
{
    socket.BeginReceive(recvBuffer, 0, recvBuffer.Length, SocketFlags.None, 
        new AsyncCallback(ReceiveDone), parser);
}

. . . . .

void ReceiveDone(IAsyncResult ar)
{
    try
    {
        int received = socket.EndReceive(ar);
        if(0 == received)
        {
            Complete();
            return;
        }
        HttpResponseParser parser = (HttpResponseParser)ar.AsyncState;
        bool cont = true;
        for(int i = 0; i < received; i++)
        {
            cont = parser.ProcessByte(recvBuffer[i]);
            if(!cont)
                break;
        }
        if(cont)
            Receive(parser);
        else
            Complete();
    }
    catch(Exception)
    {
        Complete();
    }
}

I use a very small buffer for incoming data and kind of loop socket reading requests by using callbacks. In a real HTTP client it would be a good idea to allocate a buffer at least 8K bytes of size for start and then adjust it to chunk size.

And, finally, the cleanup:

C#
public void EndGet(IAsyncResult ar)
{
    if(null == ar)
        throw new ArgumentNullException("ar");
    AsyncResult r = ar as AsyncResult;
    if((null == r) || !Object.ReferenceEquals(this.asyncResult, r))
        throw new ArgumentException();
    r.Validate();
    r.AsyncWaitHandle.WaitOne();
    r.Dispose();
    asyncResult = null;
}

Now the program itself:

C#
static void Main(string[] args)
{
    if(args.Length == 0)
    {
        Console.WriteLine("Usage: AsyncHttpGet1 <url> [<file_name>]");
        return;
    }
    Uri inputUrl = new Uri(args[0]);
    string outputFile;
    if(args.Length < 2)
        outputFile = Path.GetFileName(inputUrl.LocalPath);
    else
        outputFile = args[1];
    using(Stream output = new FileStream(outputFile, FileMode.Create))
    {
        GetPoll(inputUrl, output);
        GetCallback(inputUrl, output);
    }
}

Note the conditional compilation directives that change the program behavior. When the POLL symbol is defined the main program polls the Getter instance waiting for the competition. When the CALLBACK symbol is defined, the main program waits for user input to terminate.

As you see, the asynchronous design pattern is easy to program and simple to use.

ISynchronizeInvoke

This world would be a much better place if we kept all promises made. Unfortunately, we don't. For instance, while presenting the asynchronous design pattern, Microsoft broke it in one of the crucial places of the class library - in the System.ComponentModel.ISynchronizeInvoke interface. This interface helps delegates to get called synchronously or asynchronously in context of the other thread. The purpose of it is to communicate between worker threads and the main UI thread in WinForms applications. The interface contains three methods: Invoke, BeginInvoke and EndInvoke. The Invoke method invokes a delegate synchronously, the calling thread suspends it's execution until the call is completed. BeginInvoke/EndInvoke invoke the delegate asynchronously. The problem is that the BeginInvoke does not have either callback or state parameters violating the pattern. The only way to find out if the delegate invoke is completed or not is to poll the state of AsyncWaitHandle property. Even worse, the only implementation of the interface, the System.Windows.Forms.Control class does not require us to call the EndInvoke method. The Control's BeginInvoke implementation uses "fire and forget" principle. Thus you cannot rely on the EndInvoke to clean up the resources.

I present here my own implementation of this interface, the Synchronizer class, to demonstrate differences between the standard pattern and this particular deviation of it. I use this class in multithreaded console applications when I want to utilize the standard event handlers. The class implements ISynchronizeInvoke and IDisposable interfaces, a default constructor and a method named Poll. The console application should periodically call this method to invoke pending callbacks. Objects of this class keep a synchronized queue of marshaled delegates and a pool of private State objects that implement IAsyncResult interface. With it you may not call EndInvoke method following Microsoft's implementation. I do not dispose of State objects but reuse them. All these objects get released when the user of the Synchronizer calls it's Dispose method.

Some time ago I published the article Another way to invoke UI from a Worker Thread. In the article I suggested to move the code dealing with the ISynchronizeInvoke to the OnXXXX event method to simplify implementation of the event consumers. Since then I have developed and adopted a common pattern for declaring events. In addition to declaring an EventArgs descendant and an event delegate I also declare a static class with a single static method, Raise, that fires the event and deals with ISynchronizeInvoke issues. Take a look at the declaration of the ExceptionEvent, an event that passes an exception information from one point in the program to another (there's a standard System.Threading.ThreadExceptionEvent that performs exactly the same task but doesn't care about synchronization):

C#
public class ExceptionEventArgs: EventArgs
{
    readonly Exception exception;

    public ExceptionEventArgs(Exception exception)
    {
        this.exception = exception;
    }

    public Exception Exception
    {
        get
        {
            return exception;
        }
    }
}

public delegate void ExceptionEventHandler(object sender, 
    ExceptionEventArgs e);

#if NET20
public static class ExceptionEvent
{
#else
public sealed class ExceptionEvent
{
    private ExceptionEvent()
    {}
#endif
    public static void Raise(ExceptionEventHandler handler, object sender, 
        ExceptionEventArgs e)
    {
        if(null != handler)
        {
            foreach( 
              ExceptionEventHandler singleCast in handler.GetInvocationList())
            {
                ISynchronizeInvoke syncInvoke = 
                    singleCast.Target as ISynchronizeInvoke;
                try
                {
                    if((null != syncInvoke) && (syncInvoke.InvokeRequired))
                      syncInvoke.Invoke(singleCast, new object[] {sender, e});
                    else
                        singleCast(sender, e);
                }
                catch(Exception exception)
                {
                    throw new TargetInvocationException(exception);
                }
            }
        }
    }
}

I also declared a class named Event for the standard EventHandler delegate:

C#
#if NET20
public static class Event
{
#else
public sealed class Event
{
    private Event()
    {}
#endif
    public static void Raise(EventHandler handler, object sender, EventArgs e)
    {
        if(null != handler)
        {
            foreach(EventHandler singleCast in handler.GetInvocationList())
            {
                ISynchronizeInvoke syncInvoke = 
                    singleCast.Target as ISynchronizeInvoke;
                try
                {
                    if((null != syncInvoke) && (syncInvoke.InvokeRequired))
                      syncInvoke.Invoke(singleCast, new object[] {sender, e});
                    else
                        singleCast(sender, e);
                }
                catch(Exception exception)
                {
                    throw new TargetInvocationException(exception);
                }
            }
        }
    }
} 

With such pattern an implementation of, for instance, OnCanceled method for the Cancel event looks like:

C#
protected virtual void OnCanceled(EventArgs e)
{
    Event.Raise(Canceled, this, e);
} 

Now get back to the business.

Background workers

Asynchronous methods are good for simple (atomic) background operations. But processes like downloading a file from the Internet may take hours to complete if you're downloading, for instance, a .NET Framework SDK installation file. For such long processes the Microsoft's pattern seems a bit awkward to me.

Let's think a bit. What do we really need for a long running background operation? After it has started, we just need to know when in finishes or fails and we need a way to cancel it and get a notification that it is really cancelled to perform clean up. It would also be better if we could avoid or simplify the synchronization between workers and the main thread.

Let's define an interface to generalize these requirements into an asynchronous operation:

C#
public interface IAsyncOp
{
    event EventHandler Completed;
        
    event EventHandler Canceled;
        
    event ExceptionEventHandler Failed;
        
    bool Cancel();
} 

Now let's write another implementation of HTTP GET application, this time using my pattern.

HTTP GET application, take two

First, derive the application class from the Synchronizer, add event handlers and the Main method:

C#
public class MainClass: Synchronizer
{
    bool stop;

    bool shouldWrite;

    void Completed(object sender, EventArgs e)
    {
        Console.WriteLine("Complete!");
        stop = true;
        shouldWrite = true;
    }

    void Canceled(object sender, EventArgs e)
    {
        Console.WriteLine("Canceled.");
        stop = true;
    }

    void Failed(object sender, ExceptionEventArgs e)
    {
        Console.WriteLine("Failed: {0}", e.Exception);
        stop = true;
    }

    void Run(Uri uri, string outputFileName)
    {
        Getter getter = new Getter();
        getter.Completed += new EventHandler(Completed);
        getter.Canceled += new EventHandler(Canceled);
        getter.Failed += new ExceptionEventHandler(Failed);
        getter.Start(uri);
        stop = false;
        while(!stop)
            Poll();
        if(shouldWrite)
            using(Stream output = new FileStream(outputFileName, 
                FileMode.Create))
                output.Write(getter.Result, 0, getter.Result.Length);
    }

    static void Main(string[] args)
    {
        new MainClass().Run(new Uri(args[0]), args[1]);
    }
}

Now write the new version of the Getter class that implements our IAsyncOp interface. The code that parses the HTTP response byte stream is the same:

C#
public class Getter: IAsyncOp, IHttpResponseHandler
{
    Uri uri;
        
    Stream output;
    
    Socket socket;
    
    volatile bool cancel;
    
    . . . . .
        
    public event EventHandler Completed;
    
    public event EventHandler Canceled;
    
    public event ExceptionEventHandler Failed;
    
    public void Start(Uri uri, Stream output)
    {
        this.uri = uri;
        this.output = output;
        this.socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream,
            ProtocolType.Tcp);
        Dns.BeginGetHostByName(uri.Host, new AsyncCallback(GetHostByNameDone),
            null);
    }

    void GetHostByNameDone(IAsyncResult ar)
    {
        if(cancel)
        {
            OnCanceled(EventArgs.Empty);
            Stop();
        }
        try
        {
            IPHostEntry ihe = Dns.EndGetHostByName(ar);
            IPAddress address = ihe.AddressList[0];
            IPEndPoint trackerEndpoint = new IPEndPoint(address, uri.Port);
            socket.BeginConnect(trackerEndpoint, 
                new AsyncCallback(ConnectDone), null);
        }
        catch(Exception e)
        {
            Fail(e);
        }
    }

    . . . . .

    void Complete()
    {
        OnCompleted(EventArgs.Empty);
        Stop();
    }

    void Fail(Exception e)
    {
        OnFailed(new ExceptionEventArgs(e));
        Stop();
    }

    void Stop()
    {
        if(null != socket)
        {
            socket.Close();
            socket = null;
        }
    }

    public bool Cancel()
    {
        cancel = true;
        return true;
    }

    . . . . .
} 

I do not use threads here, only asynchronous I/O. The standard event system is used to notify the caller. The only peculiar detail is the volatile keyword for a boolean flag that notifies the callbacks of user actions. CLR guarantees that field assignments are atomic so I don't need the synchronization here. The keyword is required to tell the JIT compiler to not to optimize the accesses to the field.

Conclusion

The asynchronous design pattern provided by Microsoft is powerful, elegant and simple to implement. However, it is not universal and sometimes you need to rely on other means like a bit more general asynchronous operation pattern.

License

All the code in Pvax.Uitls namespace and it's enclosed namespaces are covered by BSD-compatible license. The rest of the code is public domain, you may use and modify it freely, you don't event have to mention my name.

Compiling sample applications

As usual, I provide a SharpDevelop 1.x combine and a NAnt 0.85.x build file. The project consists of three console applications: AsyncMethod, AsyncHttpGet1 and AsyncHttpGet2 and of a library Shared that contains common code.

SharpDevelop combine has four subprojects, all of them define two build targets, Debug and Release. For Debug targets I define following symbols: DEBUG, TRACE and NET11. For Release targets I define symbols: TRACE and NET11. If you change NET11 to NET20 some code becomes compatible with .Net 2.x features (mostly static classes). Also for AsyncHttpGet1 subproject you can add either POLL or CALLBACK symbols or it won't compile.

NAnt build files are bit simpler. Each subproject is in in it's respective folder and has it's own build file. The main build file calls each subproject build file for the specific target. The main file also specifies the third target, Clean that removes all intermediate and target files.

To all VS users - I'm sorry, no solution file so far, I have no VS installed around. But I hope you can reconstruct the solution with my description of the sample project.

History

  • 8/22/2006 - initial version of the document.

Links

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
Russian Federation Russian Federation
I'm a system administrator from Moscow, Russia. Programming is one of my hobbies. I presume I'm one of the first Russians who created a Web site dedicated to .Net known that time as NGWS. However, the Web page has been abandoned a long ago.

Comments and Discussions

 
QuestionAbout IfCompleted flag and alternatives Pin
Abdullah Al-Ageel8-Feb-09 7:18
Abdullah Al-Ageel8-Feb-09 7:18 
GeneralGood Article + Some Comments on Async Loops Pin
ggeurts24-Jul-06 19:24
ggeurts24-Jul-06 19:24 
GeneralRe: Good Article + Some Comments on Async Loops Pin
Alexey A. Popov25-Jul-06 19:52
Alexey A. Popov25-Jul-06 19:52 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.