When you’re writing applications that use asynchronous callbacks (i.e. Silverlight, AJAX, or GWT) you’ll eventually run into the problem of keeping track of the context that a request is being done in. This isn’t a problem in synchronous programming, because local variables continue to exist after one function calls another function synchronously:
int AddToCount(int amount,string countId) {
int countValue=GetCount(countId);
return countValue+amount;
}
This doesn’t work if the GetCount function is asynchronous, where we need to write something like
int AddToCountBegin(int amount,string countId,CountCallback outerCallback) {
GetCountBegin(countId,AddToCountCallback);
}
void AddToCountCallback(int countValue) {
... some code to get the values of amount and outerCallback ...
outerCallback(countValue+amount);
}
Several things change in this example: (i) the AddToCount function gets broken up into two functions: one that does the work before the GetCount invocation, and one that does the work after GetCount completes. (ii) We can’t return a meaningful value from AddToCountCallback, so it needs to ‘return’ a value via a specified callback function. (iii) Finally, the values of outerCallback and amount aren’t automatically shared between the functions, so we need to make sure that they are carried over somehow.
There are three ways of passing context from a function that calls and asynchronous function to the callback function:
- As an argument to the callback function
- As an instance variable of the class of which the callback function is a class
- Via a closure
Let’s talk about these alternatives:
1. Argument to the Callback Function
In this case, a context object is passed to the asynchronous function, which passes the context object to the callback. The advantage here is that there aren’t any constraints on how the callback function is implemented, other than by accepting the context object as a callback. In particular, the callback function can be static. A major disadvantage is that the asynchronous function has to support this: it has to accept a state object which it later passes to the callback function.
The implementation of HttpWebRequest.BeginGetResponse(AsyncCallback a,Object state) in the Silverlight libraries is a nice example. If you wish to pass a context object to the AsyncCallback, you can pass it in the second parameter, state. Your callback function will implement the AsyncCallback delegate, and will get something that implements IAsyncResult as a parameter. The state that you passed into BeginGetResponse will come back in the IAsyncResult.AsyncState property. For example:
class MyHttpContext {
public HttpWebRequest Request;
public SomeObject FirstContextParameter;
public AnotherObject AnotherContextParameter;
}
protected void myHttpCallback(IAsyncResult abstractResult) {
MyHttpContext context = (MyHttpContext) abstractResult.AsyncState;
HttpWebResponse Response=(HttpWebResponse) context.Request.EndGetResponse(abstractResult);
}
public doHttpRequest(...) {
...
MyHttpContext context=new MyHttpContext();
context.Request=Request;
context.FirstContextParameter = ... some value ...;
context.AnotherContextParameter = .. another value ...;
Request.BeginGetResponse();
Request.Callback(myHttpCallback,context);
}
Note that, in this API, the Request object needs to be available in myHttpCallback because myHttpCallbacks get the response by calling the HttpWebResponse.EndGetResponse() method. We could simply pass the Request object in the state parameter, but we’re passing an object we defined, myHttpCallback, because we’d like to carry additional state into myHttpCallback.
Note that the corresponding method for doing XMLHttpRequests in GWT, the use of a RequestBuilder object doesn’t allow using method (1) to pass context information — there is no state parameter. in GWT you need to use method (2) or (3) to pass context at the RequestBuilder or GWT RPC level. You’re free, of course, to use method (1) when you’re chaining asynchronous callbacks: however, method (2) is more natural in Java where, instead of a delegate, you need to pass an object reference to designate a callback function.
2. Instance Variable Of The Callback Function’s Class
Functions (or Methods) are always attached to a class in C# and Java: thus, the state of a callback function can be kept in either static or instance variables of the associated class. I don’t advise using static variables for this, because it’s possible for more than one asynchronous request to be flight at a time: if two request store state in the same variables, you’ll introduce race conditions that will cause a world of pain. (see how race conditions arise in asynchronous communications.)
Method 2 is particularly effective when both the calling and the callback functions are methods of the same class. Using objects whose lifecycle is linked to a single asynchronous request is an effective way to avoid conflicts between requests (see the asynchronous command pattern and asynchronous functions.)
Here’s an example, lifted from the asynchronous functions article:
public class HttpGet : IAsyncFunction<String>
{
private Uri Path;
private CallbackFunction<String> OuterCallback;
private HttpWebRequest Request;
public HttpGet(Uri path)
{
Path = path;
}
public void Execute(CallbackFunction<String> outerCallback)
{
OuterCallback = outerCallback;
try
{
Request = (HttpWebRequest)WebRequest.Create(Path);
Request.Method = "GET";
Request.BeginGetRequestStream(InnerCallback,null);
}
catch (Exception ex)
{
OuterCallback(CallbackReturnValue<String>.CreateError(ex));
}
}
public void InnerCallback(IAsyncResult result)
{
try
{
HttpWebResponse response = (HttpWebResponse) Request.EndGetResponse(result);
TextReader reader = new StreamReader(response.GetResponseStream());
OuterCallback(CallbackReturnValue<String>.CreateOk(reader.ReadToEnd()));
} catch(Exception ex) {
OuterCallback(CallbackReturnValue<String>.CreateError(ex));
}
}
}
Note that two pieces of context are being passed into the callback function: an HttpWebRequest object named Request (necessary to get the response) and a CallbackFunction<String> delegate named OuterCallback that receives the return value of the asynchronous function.
Unlike Method 1, Method 2 makes it possible to keep an unlimited number of context variables that are unique to a particular case in a manner that is both typesafe and oblivious to the function being called — you don’t need to cast an Object to something more specific, and you don’t need to create a new class to hold multiple variables that you’d like to pass into the callback function.
Method 2 comes into it’s own when it’s used together with polymorphism, inheritance and initialization patterns such as the factory pattern: if the work done by the requesting and callback methods can be divided into smaller methods, a hierarchy of asynchronous functions or commands can reuse code efficiently.
3. Closures
In both C# and Java, it’s possible for a method defined inside a method to have access to variables in the enclosing method. In C# this is a matter of creating an anonymous delegate, while in Java it’s necessary to create an anonymous class.
Using closures results in the shortest code, if not the most understandable code. In some cases, execution proceeds in a straight downward line through the code — much like a synchronous version of the code. However, people sometimes get confused the indentation, and, more seriously, parameters after the closure definition and code that runs immediately after the request is fired end up in an awkward place (after the definition of the callback function.)
public class HttpGet : IAsyncFunction<String>
{
private Uri Path;
public HttpGet(Uri path)
{
Path = path;
}
public void Execute(CallbackFunction<String> outerCallback)
{
OuterCallback = outerCallback;
try
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(Path);
Request.Method = "GET";
Request.BeginGetRequestStream(delegate(IAsyncResult result) {
try {
response = request.EndGetResponse(result);
TextReader reader = new StreamReader(response.GetResponseStream());
outerCallback(CallbackReturnValue<String>.CreateOk(reader.ReadToEnd()));
} catch(Exception ex) {
outerCallback(CallbackReturnValue<String>.CreateError(ex));
}
},null); // <--- note parameter value after delegate definition
}
catch (Exception ex)
{
outerCallback(CallbackReturnValue<String>.CreateError(ex));
}
}
}
The details are different in C# and Java: anonymous classes in Java can access local, static and instance variables from the enclosing context that are declared final — this makes it impossible for variables to be stomped on while an asynchronous request is in flight. C# closures, on the other hand, can access only local variables: most of the time this prevents asynchronous requests from interfering with one another, unless a single method fires multiple asynchronous requests, in which case counter-intuitive things can happen.
Conclusion
In addition to receiving return value(s), callback functions need to know something about the context they run in: to write reliable applications, you need to be conscious of where this information is; better yet, a strategy for where you’re going to put it. Closures, created with anonymous delegates (C#) or classes (Java) produce the shortest code, but not necessarily the clearest. Passing context in an argument to the callback function requires the cooperation of the called function, but it makes few demands on the calling and callback functions: the calling and callback functions can both be static. When a single object contains both calling and callback functions, context can be shared in a straightforward and typesafe manner; and when the calling and callback functions can be broken into smaller functions, opportunities for efficient code reuse abound.