Difference between revisions of "EventThread Pattern"
m |
m |
||
(25 intermediate revisions by the same user not shown) | |||
Line 1: | Line 1: | ||
− | + | When desining an event-listener model, the developer has to make three decisions. First, should notifications use listeners or events. Second, should notifications be processed immediately or queued for later handling. Third, should notifications be handled in the observable's executing thread or some other. ''Event Thread'' pattern addresses the second and the third question. Not only is the solution simple, but also versatile. One implementation, many models can be solved: ''Run in current thread'', ''Collects events'', ''Run in UI Thread'', ''Run in Worker''. Here goes. | |
− | When | ||
− | ====Event Thread==== | + | ====Event Thread Pattern==== |
− | In EventThread pattern, the Listener/Observer interface has a function that allows the implementation to decide the executing environment of the event. Java ([http://download.oracle.com/javase/6/docs/api/java/util/concurrent/ | + | In EventThread pattern, the Listener/Observer interface has a function that allows the implementation to decide the executing environment of the event. Java ([http://download.oracle.com/javase/6/docs/api/java/util/concurrent/Executor.html Executor]) is an interface that has various default implementations (See [http://download.oracle.com/javase/6/docs/api/java/util/concurrent/Executors.html Executors]). Many models are supported. Work can be executed in current thread, executed in new thread, or placed in a work queue, or directed to an EDT ([http://en.wikipedia.org/wiki/Event_dispatching_thread EventDispatchThread]). |
<syntaxhighlight lang="java"> | <syntaxhighlight lang="java"> | ||
public class MyObservableObject { | public class MyObservableObject { | ||
... | ... | ||
− | void addListener(MyListener listener) | + | void addListener(MyListener listener) { ... } |
} | } | ||
public interface MyListener { | public interface MyListener { | ||
Line 16: | Line 15: | ||
/** | /** | ||
* Get the executor environment where the event will be handled. | * Get the executor environment where the event will be handled. | ||
+ | * <tt>null</tt> value denotes that the events is handled immediately | ||
+ | * and in the caller's thread. | ||
* | * | ||
− | * @return executor | + | * @return executor or <tt>null</tt> |
*/ | */ | ||
Executor getExecutor(); | Executor getExecutor(); | ||
Line 23: | Line 24: | ||
} | } | ||
− | obj.addListener( | + | // Caller Usage |
+ | MyObservable obj = ... ; | ||
+ | obj.addListener( new MyListener() { | ||
+ | @Override | ||
+ | public void onEvent(Object sender, Object event) { | ||
+ | } | ||
+ | |||
+ | @Override | ||
+ | public Executor getExecutor() { | ||
+ | return CURRENT_THREAD; | ||
+ | } | ||
+ | } ); | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | ==== | + | ====Variation==== |
− | + | In an variation, the listener doesn't have ''getExecutor()'', but instead the executor environment is given as argument to observable's ''addListener(Listener, Executor)''. | |
+ | <syntaxhighlight lang="java"> | ||
− | + | public class MyObservableObject { | |
+ | ... | ||
+ | void addListener(MyListener listener, Executor executor) { ... } | ||
+ | } | ||
public interface MyListener { | public interface MyListener { | ||
− | |||
− | |||
void onEvent(Object sender, Object event); | void onEvent(Object sender, Object event); | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
} | } | ||
+ | |||
+ | // Caller Usage | ||
+ | MyObservable obj = ... ; | ||
+ | obj.addListener( myListener, CURRENT_THREAD ); | ||
+ | obj.addListener( myListener, myWorkQueue ); | ||
+ | obj.addListener( myListener, AWT_EDT ); // or SWT_EDT | ||
+ | obj.addListener( myListener, Executors.newSingleThreadScheduledExecutor() ); | ||
</syntaxhighlight> | </syntaxhighlight> | ||
+ | =====Implementation===== | ||
− | + | The penalty in the example is that a new Runnable is created for every event invocation. If firing frequency is high, this can be reduced with some ugly tricks. Current thread can be detected and event ran directly, runnable can be embedded in event (like AWT), runnable recycled. Note also, foreach listeners produces unnecessary iterator object, but this is implementation details. | |
− | |||
− | |||
− | |||
− | |||
<syntaxhighlight lang="java"> | <syntaxhighlight lang="java"> | ||
− | public | + | public class MyObservableObject { |
− | |||
− | |||
− | + | CopyOnWriteArrayList<MyListenerEntry> listeners = new CopyOnWriteArrayList<MyListenerEntry>(); | |
− | + | ||
− | public | + | void addListener(MyListener listener, Executor executor) { |
− | + | MyListenerEntry entry = new MyListenerEntry(); | |
+ | entry.listener = listener; | ||
+ | entry.executor = executor; | ||
+ | } | ||
+ | |||
+ | void notifyListeners(final Object event) { | ||
+ | for (final MyListenerEntry e : listeners) { | ||
+ | e.executor.execute( new Runnable() { | ||
+ | public void run() { | ||
+ | e.listener.onEvent(MyObservableObject.this, event); | ||
+ | }} | ||
+ | ); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | static class MyListenerEntry { | ||
+ | MyListener listener; | ||
+ | Executor executor; | ||
} | } | ||
+ | |||
} | } | ||
− | |||
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | ====Listener Adapter==== | |
− | ====Adapter==== | ||
Some listeners have default implementation called ''adapter'' (For example AWT Listeners & Adapters). With this pattern it is a good idea to have a default executor in the adapter. | Some listeners have default implementation called ''adapter'' (For example AWT Listeners & Adapters). With this pattern it is a good idea to have a default executor in the adapter. | ||
Line 81: | Line 103: | ||
} | } | ||
− | MyObservable | + | // Caller Usage |
− | + | MyObservable obj = ... ; | |
+ | obj.addListener( new MyAdapter() { | ||
@Override | @Override | ||
public void onEvent(Object sender, Object event) { | public void onEvent(Object sender, Object event) { | ||
Line 90: | Line 113: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
+ | ====Utils==== | ||
+ | First, use [http://download.oracle.com/javase/6/docs/api/java/util/concurrent/Executors.html Executors] if possible. If you need to run in UI thread or current thread use the following util. | ||
− | |||
− | |||
<syntaxhighlight lang="java"> | <syntaxhighlight lang="java"> | ||
− | public class | + | public class Executors2 { |
− | void | + | |
+ | // Executor that runs in current thread | ||
+ | public static Executor CURRENT_THREAD = new CurrentThreadExecutor(); | ||
+ | |||
+ | // Async executor queues the command into AWT event queue | ||
+ | public static Executor AWT_EDT = new AWTExecutorAsync(); | ||
+ | |||
+ | // Sync executor blocks the call until the command is ran and finished in AWT Thread | ||
+ | // for MyObservableObject, this is effectively same as CallBack - wait for all listeners | ||
+ | public static Executor AWT_EDT_SYNC = new AWTExecutorSync(); | ||
+ | |||
+ | public static Executor createSWTExecutor(Display display, boolean async) { | ||
+ | return async ? new SWTExecutorAsync(display) : new SWTExecutorSync(display); | ||
+ | } | ||
+ | |||
+ | } | ||
+ | |||
+ | class AWTExecutorAsync implements Executor { | ||
+ | |||
+ | @Override | ||
+ | public void execute(Runnable command) { | ||
+ | EventQueue.invokeLater(command); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | class AWTExecutorSync implements Executor { | ||
+ | |||
+ | @Override | ||
+ | public void execute(Runnable command) { | ||
+ | if (EventQueue.isDispatchThread()) | ||
+ | { | ||
+ | command.run(); | ||
+ | } else { | ||
+ | try { | ||
+ | EventQueue.invokeAndWait(command); | ||
+ | } catch (InterruptedException e) { | ||
+ | throw new RuntimeException(e); | ||
+ | } catch (InvocationTargetException e) { | ||
+ | throw new RuntimeException(e); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | |||
+ | class CurrentThreadExecutor implements Executor { | ||
+ | @Override | ||
+ | public void execute(Runnable command) { | ||
+ | command.run(); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | class SWTExecutorAsync implements Executor { | ||
+ | |||
+ | Display display; | ||
+ | public SWTExecutorAsync(Display display) | ||
+ | { | ||
+ | this.display = display; | ||
+ | } | ||
+ | |||
+ | @Override | ||
+ | public void execute(Runnable command) { | ||
+ | // Don't accept work if the SWT thread is disposed. | ||
+ | if (display.isDisposed()) | ||
+ | throw new RuntimeException("The SWT thread has been disposed"); | ||
+ | display.asyncExec(command); | ||
+ | } | ||
+ | |||
+ | } | ||
+ | |||
+ | class SWTExecutorSync implements Executor { | ||
+ | |||
+ | Display display; | ||
+ | public SWTExecutorSync(Display display) | ||
+ | { | ||
+ | this.display = display; | ||
+ | } | ||
+ | |||
+ | @Override | ||
+ | public void execute(Runnable command) { | ||
+ | // Don't accept work if the SWT thread is disposed. | ||
+ | if (display.isDisposed()) | ||
+ | throw new RuntimeException("The SWT thread has been disposed"); | ||
+ | display.syncExec(command); | ||
} | } | ||
+ | |||
+ | } | ||
− | |||
− | |||
− | |||
</syntaxhighlight> | </syntaxhighlight> | ||
Latest revision as of 08:45, 14 January 2011
When desining an event-listener model, the developer has to make three decisions. First, should notifications use listeners or events. Second, should notifications be processed immediately or queued for later handling. Third, should notifications be handled in the observable's executing thread or some other. Event Thread pattern addresses the second and the third question. Not only is the solution simple, but also versatile. One implementation, many models can be solved: Run in current thread, Collects events, Run in UI Thread, Run in Worker. Here goes.
Event Thread Pattern
In EventThread pattern, the Listener/Observer interface has a function that allows the implementation to decide the executing environment of the event. Java (Executor) is an interface that has various default implementations (See Executors). Many models are supported. Work can be executed in current thread, executed in new thread, or placed in a work queue, or directed to an EDT (EventDispatchThread).
<syntaxhighlight lang="java"> public class MyObservableObject { ... void addListener(MyListener listener) { ... } } public interface MyListener {
void onEvent(Object sender, Object event);
/** * Get the executor environment where the event will be handled. * null value denotes that the events is handled immediately * and in the caller's thread. * * @return executor or null */ Executor getExecutor();
}
// Caller Usage MyObservable obj = ... ; obj.addListener( new MyListener() { @Override public void onEvent(Object sender, Object event) { }
@Override public Executor getExecutor() { return CURRENT_THREAD; } } ); </syntaxhighlight>
Variation
In an variation, the listener doesn't have getExecutor(), but instead the executor environment is given as argument to observable's addListener(Listener, Executor). <syntaxhighlight lang="java">
public class MyObservableObject { ... void addListener(MyListener listener, Executor executor) { ... } } public interface MyListener { void onEvent(Object sender, Object event); }
// Caller Usage MyObservable obj = ... ; obj.addListener( myListener, CURRENT_THREAD ); obj.addListener( myListener, myWorkQueue ); obj.addListener( myListener, AWT_EDT ); // or SWT_EDT obj.addListener( myListener, Executors.newSingleThreadScheduledExecutor() ); </syntaxhighlight>
Implementation
The penalty in the example is that a new Runnable is created for every event invocation. If firing frequency is high, this can be reduced with some ugly tricks. Current thread can be detected and event ran directly, runnable can be embedded in event (like AWT), runnable recycled. Note also, foreach listeners produces unnecessary iterator object, but this is implementation details.
<syntaxhighlight lang="java"> public class MyObservableObject {
CopyOnWriteArrayList<MyListenerEntry> listeners = new CopyOnWriteArrayList<MyListenerEntry>();
void addListener(MyListener listener, Executor executor) { MyListenerEntry entry = new MyListenerEntry(); entry.listener = listener; entry.executor = executor; }
void notifyListeners(final Object event) { for (final MyListenerEntry e : listeners) { e.executor.execute( new Runnable() { public void run() { e.listener.onEvent(MyObservableObject.this, event); }} ); } }
static class MyListenerEntry { MyListener listener; Executor executor; }
} </syntaxhighlight>
Listener Adapter
Some listeners have default implementation called adapter (For example AWT Listeners & Adapters). With this pattern it is a good idea to have a default executor in the adapter.
<syntaxhighlight lang="java"> public abstract class MyAdapter implements MyListener {
public Executor getExecutor() { return CURRENT_THREAD; } }
// Caller Usage MyObservable obj = ... ; obj.addListener( new MyAdapter() { @Override public void onEvent(Object sender, Object event) { ... } } ); </syntaxhighlight>
Utils
First, use Executors if possible. If you need to run in UI thread or current thread use the following util.
<syntaxhighlight lang="java">
public class Executors2 {
// Executor that runs in current thread public static Executor CURRENT_THREAD = new CurrentThreadExecutor();
// Async executor queues the command into AWT event queue public static Executor AWT_EDT = new AWTExecutorAsync();
// Sync executor blocks the call until the command is ran and finished in AWT Thread // for MyObservableObject, this is effectively same as CallBack - wait for all listeners public static Executor AWT_EDT_SYNC = new AWTExecutorSync();
public static Executor createSWTExecutor(Display display, boolean async) { return async ? new SWTExecutorAsync(display) : new SWTExecutorSync(display); }
}
class AWTExecutorAsync implements Executor {
@Override public void execute(Runnable command) {
EventQueue.invokeLater(command);
} }
class AWTExecutorSync implements Executor {
@Override public void execute(Runnable command) { if (EventQueue.isDispatchThread()) { command.run(); } else { try { EventQueue.invokeAndWait(command); } catch (InterruptedException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { throw new RuntimeException(e); } } } }
class CurrentThreadExecutor implements Executor { @Override public void execute(Runnable command) { command.run(); } }
class SWTExecutorAsync implements Executor {
Display display; public SWTExecutorAsync(Display display) { this.display = display; }
@Override public void execute(Runnable command) { // Don't accept work if the SWT thread is disposed. if (display.isDisposed()) throw new RuntimeException("The SWT thread has been disposed"); display.asyncExec(command); }
}
class SWTExecutorSync implements Executor {
Display display; public SWTExecutorSync(Display display) { this.display = display; }
@Override public void execute(Runnable command) { // Don't accept work if the SWT thread is disposed. if (display.isDisposed()) throw new RuntimeException("The SWT thread has been disposed"); display.syncExec(command); }
}
</syntaxhighlight>
--