问题
My issue is pretty identical to Guava EventBus dispatching, but while the underlying issue is similar, my attempted fix has left me in the dark.
I have 2 events that are fired back to back. The 2nd event is dependant on the final state of the first event after all handlers are done with it. It should only fire if the 1st event wasn't cancelled. The catch is that both of these events are fired from within another event's handler.
So while I don't care who listens to the 1st nested event, I do care what they have to say about it. Am I already leaving the problemspace that events and Guava's EventBus aim to solve?
Considering:
public void parentEventHandler(ParentEvent parentEvent) {
Object nestedEvent = createNestedEvent();
eventBus.post(nestedEvent);
if(nestedEvent.isCancelled()) {
return;
}
Object anotherNestedEvent = createOtherNestedEvent();
eventBus.post(anotherNestedEvent);
}
What I expected:
1. parentEvent is posted
2. parentEventHandler is called
3. nestedEvent is posted
4. handlers of nestedEvent are called
5. finished handling nestedEvent
6. if statement checks for cancel state of nestedEvent
7. anotherNestedEvent is posted if nestedEvent not cancelled
8. handlers of anotherNestedEvent are called
9. finished handling anotherNestedEvent
10 finished handling parentEvent
What is happening:
1. parentEvent is posted
2. parentEventHandler is called
3. nestedEvent is posted
4. if statement checks for cancel state of nestedEvent (defaults to false)
5. anotherNestedEvent is posted
6. finished handing parentEvent
7. handlers of nestedEvent are called
8. nestedEvent is cancelled (too late now)
9. finished handling nestedEvent
10 handlers of anotherNestedEvent are called
11 finished handling anotherNestedEvent
At point 8. regardless of whether the handler cancels the event the second event has already been queued since the cancel check defaults to false. Guava's EventBus insists on finishing its current handler run before starting the next event's, which I'm sure has its uses, but its not what I'm looking for.
Attempted hack:
I noticed that Guava has an implementation for an ImmediateDispatcher (https://github.com/google/guava/blob/master/guava/src/com/google/common/eventbus/Dispatcher.java#L179) available that publishes events as they come in contrary to the behaviour of saving events until the current one has been handled by all subscribers of the default PerThreadQueuedDispatcher (https://github.com/google/guava/blob/master/guava/src/com/google/common/eventbus/Dispatcher.java#L73).
However, these other dispatchers are package private and there's no public API on the EventBus to change the dispatcher to use. Forking Guava and changing the default dispatcher at https://github.com/google/guava/blob/master/guava/src/com/google/common/eventbus/EventBus.java#L122 and L136 to Dispatcher.immediate()
, re-installing Guava locally under another version number, and fat jarring that custom build into my project the observed behaviour of events in the application hasn't changed at all. And now I'm completely lost.
Is there a way to achieve strict LIFO event dispatching with Guava's EventBus or is there a different paradigm I should be looking at rather than events that would make more sense when events can be cancelled and frequently end up nested in other event handlers? I don't care how many and which subscribers listen to the events, but I do want to know what they have to say about the event (i.e. whether they decided if it should be cancelled or not). The application is entirely single threaded.
回答1:
If you're using the "regular" EventBus
, you can get this to work by creating a secondary event.
Add an interface InternalEventCallback
and a class InternalEventCallbackHandler
:
interface InternalEventCallback {
void run();
}
class InternalEventCallbackHandler {
@Subscribe
public void internalEventHandler(InternalEventCallback r){
r.run();
}
}
Where you are creating your EventBus
, register the InternalEventCallbackHandler
:
eventBus.register(new InternalEventCallbackHandler());
Then in your parentEventHandler
do:
@Subscribe
public void parentEventHandler(ParentEvent parentEvent) {
NestedEvent nestedEvent = createNestedEvent();
eventBus.post(nestedEvent);
eventBus.post(new InternalEventCallback() {
@Override
public void run() {
if(nestedEvent.isCancelled()) {
return;
}
Object anotherNestedEvent = createOtherNestedEvent();
eventBus.post(anotherNestedEvent);
}
});
}
Edit:
If you use the AsyncEventBus
together with the "direct executor", you can get the same behaviour as in the example above, but without InternalEventCallback
and InternalEventCallbackHandler
EventBus eventBus = new AsyncEventBus(MoreExecutors.newDirectExecutorService());
回答2:
Turns out my custom Guava build was being overwritten by an already bundled older version of Guava from the host application (ofcourse it did). I ended up applying the hack through code (probably not very resilient to version changes):
public GuavaEventService() {
this.bus = new EventBus();
try {
Field dispatcherField = EventBus.class.getDeclaredField("dispatcher");
dispatcherField.setAccessible(true);
Class<?> dispatcherClass = dispatcherField.get(this.bus).getClass().getSuperclass();
Method immediateDispatcher = dispatcherClass.getDeclaredMethod("immediate");
immediateDispatcher.setAccessible(true);
dispatcherField.set(this.bus, immediateDispatcher.invoke(null));
} catch (Exception ex) {
throw new IllegalStateException("Failed to initialize event service dispatcher: " + ex.getMessage());
}
}
However, eiden suggested a much cleaner Async event bus alternative in the edit of his answer:
EventBus eventBus = new AsyncEventBus(MoreExecutors.newDirectExecutorService());
来源:https://stackoverflow.com/questions/53133014/guava-eventbus-delaying-handler-execution-of-nested-events