DSF Common Patterns
Summary
DSF
- Customizing, componentization, performance.
Asynchronous Methods
One of the central features of DSF is that it relies very heavily on
the use of asynchronous methods. Asynchronous methods here mean
simply methods that use a callback
object to indicate their completion. The use of asynchronous
methods can be very contageous in a system, where if a lower level API
is composed of asynchronous methods, a higher level system which uses
those methods also has to have asynchronous methods in its interface
(or risk blocking its calling thread).
TODO? : diagram of a layered system with asynchronous APIs
Request Monitor
There is a standard callback object used in DSF, the request
monitor. A request monitor has the following features:
- Executor - A
argument to the request monitor constructor allows the user to specify
what executor should be used to invoke the callback method.
- Status -
Asynchronous methods that take a callback can always set the status
indicating the success or failure of the call.
- Callback Methods
- The request monitor declares several protected methods which are
invoked when the callback is invoked: handleCompleted(), handleOK(),
handleError(), etc. The users may override these methods as
needed to perform additional processing upon asynchronous method
completion.
- Parent Request Monitor
- If the method calling an asynchronous method is itself asynchronous,
it may set its argument request monitor as the parent of the request
monitor it is creating. The parent request monitor will be
automatically invoked when the lower level request monitor is completed.
Following is the snippet from a the "hello world" example of using a
request monitor:
26: public class AsyncHelloWorld {
28: public static void main(String[] args) {
29: Executor executor = ImmediateExecutor.getInstance();
30: RequestMonitor rm = new RequestMonitor(executor, null);
31: asyncHelloWorld(rm);
32: }
34: static void asyncHelloWorld(RequestMonitor rm) {
35: System.out.println("Hello world");
36: rm.done();
37: }
- Line 29 creates an "immediate executor". Unlike more
sophisticated executors, the immediate executor simply invokes the
runnable it receives immediately. It does not use any threads and
it will never throw a RejectedExecutionException.
- Line 30 creates the request monitor. This program does not
perform any processing after the callback is invoked, so it does not
override RequestMonitor's completion methods.
- Line 36 marks the callback as completed and implicilty invokes
the callback method. As a contract with the caller, the
asynchronous method has to invoke done() when its finished. As
there is no compiler support for ensuring that the asynchronous method
completes the request monitor, failure to do so results in common
but often suble and difficult to track down bugs.
Data Request Monitor
The base request monitor is useful for returning status of the
asynchronous method, but they do not have an option of returning a
value to the caller. DataRequestMonitor can be used for that
purpose. A simple example of using the data request monitor:
22: public class Async2Plus2 {
24: public static void main(String[] args) {
25: Executor executor = ImmediateExecutor.getInstance();
26: DataRequestMonitor<Integer> rm =
27: new DataRequestMonitor<Integer>(executor, null) {
28: @Override
29: protected void handleCompleted() {
30: System.out.println("2 + 2 = " + getData());
31: }
32: };
33: asyncAdd(2, 2, rm);
34: }
36: static void asyncAdd(int value1, int value2, DataRequestMonitor<Integer> rm) {
37: rm.setData(value1 + value2);
38: rm.done();
39: }
40: }
- Lines 26-27 create the data request monitor using a local class
declaraion. Note the type parameter to DataRequestMonitor allows
for compiler checking of the type when calling getData() and setData()
methods.
- Lines 29-31 override the standard callback to print the result of
the calculation to the console.
Multi-Request Monitor
A common problem when using asynchronous is that several asynchronous
methods need to be called in parallel, so the calling method needs to
somehow manage the completion of several request monitors.
CountingRequestMonitor can be used for this purpose. It is
configured such that it's done() method needs to be called a count number of times before the
callback method is invoked.
The following snipped from the AsyncQuicksort example shows a simple
example of using the CountingRequestMonitor:
42: static void asyncQuicksort(final int[] array, final int left,
43: final int right, RequestMonitor rm)
44: {
45: if (right > left) {
46: int pivot = left;
47: int newPivot = partition(array, left, right, pivot);
48: printArray(array, left, right, newPivot);
49:
50: CountingRequestMonitor countingRm = new CountingRequestMonitor(fgExecutor, rm);
51: asyncQuicksort(array, left, newPivot - 1, countingRm);
52: asyncQuicksort(array, newPivot + 1, right, countingRm);
53: countingRm.setDoneCount(2);
54: } else {
55: rm.done();
56: }
57: }
- Line 50 creates the CountingRequestMonitor. Note that the
parent request monitor is set to the request monitor from the
asyncQuicksort() argument. This parent request monitor is
automatically called when the counting request monitor is completed.
- Lines 51 and 52, use the same instance of counting request
monitor when calling the sub-routine. Each sub-routine will call
done() on the counting request monitor.
- Line 53 sets the count to the number of sub-routines called with
the counting request monitor. Note that the done count can be set
after calling the sub-routines, because the counting request monitor
will not be completed until the count is set.
- Line 55 Don't forget to complete the request monitor in all
execution paths!
Non-Executor Thread
Future
Query
Concurrency Annotations
Services
OSGi
Session
Tracker
Data Model
View Model
Adapter, Provider, Node
Timers