Tuesday, April 26, 2005

Asynchronous Delegates With C#

{

In my previous article on delegates in C#, I gave the syntactical basics of delegates as well as a very rough idea of how they could be used in a callback scenario. This time I'd like to revisit the concept of delegates but take it a step further and show you how the .NET Framework wraps the ability to make asynchronous calls right into the capabilities of delegates. In short, once an instance of a delegate is obtained, it comes with BeginInvoke(...) and EndInvoke(IAsyncResult) methods that can be used to piece together an asynchronous call.

In my previous example, we used a class called DB that had a method called RunLongQuery to fake the notion of something that takes a long time. However, it's not difficult to write code that chews away CPU and I/O cycles so this time around we'll use a class that really does take some time to do its work:

public class FileMapper{
public string MapHeirarchy(string startPoint, int thresh, bool top){...}
}


Our class, FileMapper, has a method called MapHeirarchy which takes a directory path, a threshold, and a boolean flag as its respective arguments. The overall goal of this method is simple: you pass it a path and it will graphically map out the directory structure. The integer and boolean arguments are there because the method is called recursively. So if I wrote the following code:

FileMapper map = new FileMapper();
map.MapHeirarchy("C:\\CODE", 0, true);


I'd get a list of the folders underneath the code directory returned as a string. It would look like:

Mapping C:\CODE
-PROJA
--PROJASUB
--PROJASUB2
-PROJB
--PROJBSUB
--PROJBSUB2


Not the most exciting thing in the world, but it can take a long time. For example, to map my C:\Program Files directory took 1 minute and 58 seconds on my Thinkpad. If we have methods such as this, which will take some time to run their course, they are good candidates for an asynchronous call. Asynchronous, in this case, means that once the method call is made, our code will continue executing until we check to see that the method has completed. We don't have to write a lick of thread management code in order to get this behavior; it's all built in for us as long as we know how to define a delegate.

The syntactical basics of delegates should be straightforward if you saw the earlier article. We'll add a delegate to the FileMapper class so that we can call the MapHeirarchy method asynchronously:

public class FileMapper{
public delegate string MapAsync(string startPoint, int thresh, bool top);

public string MapHeirarchy(string startPoint, int thresh, bool top){...}
}


The MapAsync delegate matches the return value and arguments of the method we intend to use it for, MapHeirarchy. I like to call delegates "typesafe method pointers" and point out that they are "typesafe" because you can only point to a method that has the same return type and arguments.

In the class where we are leveraging the FileMapper class, we create an anonymous instance of the class to map the MapHeirarchy method of the instance to an instance of the MapAsync delegate. Sounds like a lot, but it's really just a single line of code:

FileMapper.MapAsync mp = new FileMapper.MapAsync(new FileMapper().MapHeirarchy);

In order to make the asynchronous call, rather than invoking the delegate outright like this:

mp("C:\\DIR", 0, true);

we will use the BeginInvoke method of the delegate like this:

IAsyncResult v = mp.BeginInvoke("C:\\Code05", 0, true, null, null);

From left to right, an object which implements the IAsyncResult interface is returned from an asynchronous call. This is, quite simply, the object which we can "ask" about when the method has finished executing and which will allow us to retrieve the results of the method's execution. The mp.BeginInvoke is the call to our delegated method along with the first three arguments which we've seen defined above. The last two arguments, passed as nulls in this instance can be ignored; they must be a part of the call, but we don't have to know what they are just yet.

The IAsyncResult object has an IsCompleted property which holds a boolean flag indicating whether the method has completed. We can write simple code to poll this property until the method has finished its execution:

while(!v.IsCompleted){
Console.WriteLine("Method still executing... ");
}


Under normal circumstances you may take advantage of a loop like this to display or draw an animation that allowed a user to know that processing was taking place. There are many different directions that this could be taken but it suffices to say that at this point the method you've invoked is executing asynchronously and you're free to carry on with whatever code follows it.

To retrieve the result from the method, we must again ask the IAsyncResult object. Our code may look as follows:

if(v.IsCompleted){
string tmp = mp.EndInvoke(v);
Console.Write(tmp);
}


Notice that after the IsCompleted method returns a true value we have the ability to use our delegate and call the EndInvoke method, passing it the IAsyncResult object to get our return value. The implementation is super slick here because the return type of the EndInvoke method is the return type of the original method!

So here we see delegates one step further: the ability to make an asynchronous call with a delegate without having to worry about Threads, Synchs, Timers, or Locks. We also can see a way in which any method has the potential to be made asynchronous, as long as it has an associated delegate to go along with it. Finally, remember that you can define your own delegates for any method. The delegate that allows you to make an asynchronous call does not have to be defined in the same class whose methods you want to call asynchronously.

Download the full code listing here.

Questions? david DOT(.) seruyange AT(@) gmail DOT(.) com

}

No comments: