番石榴EventBus调度

时间:2014-02-22 00:32:17

标签: java guava

我正在使用Guava的EventBus来启动一些处理和报告结果。这是一个非常简单的可编译示例:

import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;

public class Test {

    public static class InitiateProcessing { }
    public static class ProcessingStarted { }
    public static class ProcessingResults { }
    public static class ProcessingFinished { }

    public static EventBus bus = new EventBus();

    @Subscribe
    public void receiveStartRequest(InitiateProcessing evt) {
        System.out.println("Got processing request - starting processing");
        bus.post(new ProcessingStarted());

        System.out.println("Generating results");
        bus.post(new ProcessingResults());
        System.out.println("Generating more results");
        bus.post(new ProcessingResults());

        bus.post(new ProcessingFinished());
    }

    @Subscribe
    public void processingStarted(ProcessingStarted evt) {
        System.out.println("Processing has started");
    }

    @Subscribe
    public void resultsReceived(ProcessingResults evt) {
        System.out.println("got results");
    }

    @Subscribe
    public void processingComplete(ProcessingFinished evt) {
        System.out.println("Processing has completed");
    }


    public static void main(String[] args) {
        Test t = new Test();
        bus.register(t);
        bus.post(new InitiateProcessing());
    }
}

我将这些事件用作其他软件组件的一种方式,以便为此处理做准备。例如,他们可能必须在处理之前保存其当前状态并在之后恢复它。

我希望这个程序的输出是:

Got processing request - starting processing
Processing has started
Generating results
got results
Generating more results
got results
Processing has completed

相反,实际输出是:

Got processing request - starting processing
Generating results
Generating more results
Processing has started
got results
got results
Processing has completed

应该指示处理已经开始的事件实际发生在实际处理之后(“生成结果”)。

在查看源代码后,我理解为什么它会以这种方式运行。以下是EventBus的相关source code

  /**
   * Drain the queue of events to be dispatched. As the queue is being drained,
   * new events may be posted to the end of the queue.
   */
  void dispatchQueuedEvents() {
    // don't dispatch if we're already dispatching, that would allow reentrancy
    // and out-of-order events. Instead, leave the events to be dispatched
    // after the in-progress dispatch is complete.
    if (isDispatching.get()) {
        return;
    }
    // dispatch event (omitted)

发生的事情是因为我已经调度了顶级InitiateProcessing事件,其余事件只会被推到队列的末尾。我希望它的行为类似于.NET事件,在所有处理程序完成之前,调用事件不会返回。

我不太明白这个实现的原因。当然,事件保证是有序的,但周围代码的顺序完全失真。

有没有办法让总线按照描述运行并产生所需的输出?我确实读过Javadocs

  

EventBus保证不会从中调用订阅者方法   多个线程同时,除非方法明确允许   它通过承载@AllowConcurrentEvents注释。

但我不认为这适用于此 - 我在单线程应用程序中看到了这个问题。

修改

这里问题的原因是我在订阅者中post。由于事件总线不可重入,因此这些“子帖子”排队并在第一个处理程序完成后处理。我可以在if (isDispatching.get()) { return; }来源中注释EventBus部分,一切都像我期望的那样 - 所以真正的问题是我通过这样做引入了哪些潜在的问题?似乎设计师做出了一个尽责的决定,不允许重入。

4 个答案:

答案 0 :(得分:7)

EventBus通常基于以下原则运作:将事件发布到总线的代码不应关心订阅者对事件的处理方式,也不应关注事件发布的顺序何时被尊重(无论如何,在同步事件总线的情况下。)

如果您希望在方法过程中的特定时间调用特定方法,并且希望确保在方法继续之前完成这些方法(如您在示例中所示),为什么不直接调用这些方法?当您使用事件总线时,您明确地将代码与响应给定事件的确切事件分开。这在许多情况下是可取的,并且是EventBus存在的主要原因,但它似乎不是你想要的。

答案 1 :(得分:5)

我试着总结一下Guava的EventBus事件传递行为:

如果在 t1 时刻发布 E1 事件,则会通知所有订阅者。 如果其中一个订阅者在其 @Subscribe -method中发布了一个事件(稍后片刻),那么“新”事件 E2 将被排队并随后传递即可。之后意味着:毕竟来自 t1 @

将此类“级联”事件发布与广度优先树遍历进行比较。

似乎是EventBus的明确选择设计。

答案 2 :(得分:2)

虽然发布到EventBus后才会返回,直到所有“订阅者”都已发出信号为止。这些订阅者可能尚未开始执行。这意味着当第一个bus.post返回时,您继续下一个帖子,而没有任何干预用户开始处理。

  

public void post(Object event)将事件发布到所有已注册的事件   订户。事件发生后,此方法将成功返回   已发布给所有订阅者,无论是否有任何例外   订阅者抛出。如果没有订阅订阅者   事件的类,事件不是DeadEvent,它将是   包裹在DeadEvent并重新发布。

     

参数:event - 要发布的事件。

答案 3 :(得分:1)

我知道这个问题已有4岁,但今天我遇到了同样的问题。有一个简单(且违反直觉)的更改即可获得所需的行为。对于https://stackoverflow.com/a/53136251/1296767,您可以将AsyncEventBus与DirectExecutor结合使用:

public static EventBus bus = new AsyncEventBus(MoreExecutors.newDirectExecutorService());

通过上述更改运行测试代码,结果正是您想要的:

Got processing request - starting processing
Processing has started
Generating results
got results
Generating more results
got results
Processing has completed