Asynchronous function calls

Asynchronous Object Requests

Mark Hooijkaas, $Revision: 1.1.1.1 $, $Date: 2000/10/12 02:15:27 $

Introduction

This document describes the rationale behind the org.doblet.async package. It is a small package but it's use will seem kind of eccentric for people who are not used to asynchronous thinking.

Motivation

During the design of the Doblet Technology, I repeatedly encountered the following problem: An Object requests some kind of Object to be retrieved or created, but should not be allowed to wait for completion of this action. The main reason for not waiting is because the time to wait will be indeterminate, mainly due to network interaction needed to complete the request. Some scenario's where this can happen are: The org.doblet.async package, tries to provide a consistent framework, and supporting interfaces to use in such situations.

The Solution tries to achieve the following objectives:

Solution

Three interfaces are defined as follows:
package org.doblet.async;

public interface ResultListener {
  void notifyResult(Result result); 
}

public interface Request {
  void       setListener(ResultListener);
}

public interface Result {
  Object    value(); 
  Request   request();
}
The flow over time using these interfaces is a follows:
  1. A function is called
  2. The function prepares some processing (which is probably scheduled after the call returns).
  3. The function returns a Request object.
  4. The caller gets this object, and registers a ResultListener object on it.
  5. When the scheduled processing is finnished, the Request object is notified of this, and of the resulting value (a generic object).
  6. The Request notifies the ResultListener that the processing has ended, and passes the result (with a reference to the original request) in a Result object.
Notes:
ad 3. It is completely up to the asynchronous function what kind of Object is returned (provided it implements the Request interface). This will differ between different kind of functions.
ad 5. It is completely up to the function, the scheduled processing, and the Request object how it is notified when the processing is done.
ad 4, 5. Step 5 may precede step 4. I.e. the result may be known before a ResultListener is registered. In this case the ResultListener will be notified of the result as soon as it is registered. (In fact it will be notified in the registration call itself)

Implementing a asynchronous function

A asynchronous function, should return an Request as result. The function has complete freedom in what kind of object this is and how it is created, provided that
  1. It implements the Request interface.
  2. It is an unique object.
  3. It returns the object promptly (i.e. there should not be any waiting/sleeping for network info or other unpredicatble time delays.
N.B. The last requirement is not really needed, but is the main reason to use an asynchronous call mechanism anyway.
An example of such a function would be:
class Example {
  Request askConnectionPermission(ClientInfo info)
  {
    PermissionRequest req = new PermissionRequest;
    req.clientInfo = info;
    broadcast("Hey guys, should we let " + info.name() + " join us?");
    return req;
  }
}
Note that the broadcast in this example should not wait until it has an answer from all parties. Preferably, it should not even wait until all events are actually sent, but should just enqueue the messages for later sending and exit. In this example it is not shown how the PermissionRequest object knows that request is finnished and what the result is. This is the more tricky part, and depends on the exact nature of the communication.

In many cases the result will be known immediatly, and there is no need to wait. For these cases a special helper class is defined:

  package org.doblet.async;

  public class ImmediateResult implement Request, Result {
    private Object result;
    public ImmediateResult(Object result)         { this.result = result}
    public Object value()                         { return result; }
    public void   setListener(ResultListener lst) { lst.notifyResult(this); }
  }
when we rewrite the above example to immediatly know the result, using the new convenience class it will look as follows:
class Doblet {
  Request askConnectionPermission(ClientInfo info)
  {
    if (info.name()== "MARK") 
      return new ImmediateResult("OK");
    else
      return new ImmediateResult(new PermissionException());
  }
}
Note that it is possible to return an Exception as a result of a call, and this would be considered a proper way that something did not succeed. However, even if the result is a Throwable there will probably little use to actually throw such an exception, because of the asynchronous nature of the calls (see also the section on Design Considerations).

Calling an asynchronous function

Calling an asycnhronous function will in general cost a little extra effort. The reason for this is that the initiation of a call, and the processing of the result will be separated in time and in different sections of the code. Although a convenience class can prevent this, by waiting for the result, this kind of "blocking" behaviour is exactly what we want to prevent in the first place.
A typical example of a asynchronous call would be:
class PermissionResultListener {
  private ClientInfo info;
  PermissionResultListener(ClientInfo info) { this.info = info; }
  void notifyResult(Result res) {
    if ( res instanceof AcceptMessage) 
      System.out.println(info.name()+" accepted")
  }
}

main()
{
  // ....
  Request req = AskConnectionPermission(info)
  PermissionResultListener lst = new PermissionResultListener(info);
  req.setListener(lst);
  // ....
  // or even shorter
  // ....
  AskConnectionPermission(info).setListener(new PermissionResultListener(info));
}
The last line of code shows that, in principle, a synchronous function call can be coded in one statement, without declaring extra variables. The statement is a bit long, but relatively straightforward.
The only thing always needed is to define a separate method (sometimes in a dedicated class) to handle the result. This will be the mayor nuisance of any asynchronous mechanism in the all programming languages known to me.

Finally, an interesting feature to mention is that it is possible to have one object instance listen to multiple results. For each result notification it receives it can identify which request the result corresponds to by examining the request field of the Result object.
An example might be the ConnectPermission mechanism above. The code would have sent 5 requests to all client asking their permission for the new client to join. For each of these 5 Request object, the same ResultListener object instance is registered, which counts the number of Results, and knows all is well if it receives 5 acceptance messages.

Design Considerations

In this section I describe some alternative options that I did not incorporate, and especially the reasons for not doing so.

Initially the methods of the Request interface and the Result interface, were combined in one Request like interface. This interface could be asked for the result value at any time, and ask if the request was already finished or not. I discarded this solution because it made it possible to ask for a result value, before the request was finished. This made for ugly code, either returning a null value (which could be mistaken for a null result value), or throwing an exception. In the new solution, one can only ask for the result value, when the result is actually available, because one will only be handed a Result object, when the result is there.
Another factor in discarding this soltion was that it made a polling mechanism a bit too obvious (it needed a finished() method), and I consider polling inferior. If one would really want a polling mechanism, it is easy to construct a ResultListener that can be polled if the reslt already is in.

Another "feature" that I removed was the distinction between a correct result and an error result (which would be a subclass of Throwable). The idea was that such an error result could be thrown as java exception. The problem was that I used a special flag to indicate succes or failure, and had a seperate function call to get the succes result (any Object) or the error result (a Throwable). This made ugly code, because one needed to define the behaviour if a programmer asked for the error result, while the request succeeded or vice versa. Next to this, I deemed the exception throwing feature not as useful as I originally hoped. It is still possible to throw an exception if the result value is a Throwable, but there seems little use for it, because of the asynchronous nature. The exception result will arrive "out of nothing" (from some kind of event dispatching loop). This loop/thread will probably not be able to do anything useful with the exception, and will discard it.

My main problem with the above solution is that it is not type safe. All results are passed as Object, so they must be cast to the appropriate type. This kind of solution is pretty common in java, mainly because of the lack of C++ like templates. Until java provides a C++ like template mechanism, I see no way to elegantly solve this problem, except for defining the above interfaces for different Result types as needed.