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.
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:
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:
Request
object.
ResultListener
object on it.
Request
object is notified of this, and of the
resulting value (a generic object).
Request
notifies the
ResultListener
that the processing has ended, and
passes the result (with a reference to the original request) in a
Result
object.
Request
interface).
This will differ between different kind of functions.
Request
object how it is notified
when the processing is done.
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)
Request
as result.
The function has complete freedom in what kind of object this is and how
it is created, provided that
Request
interface.
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).
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.
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.
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.