关于java:Guava EventBus调度

Guava EventBus dispatching

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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());
    }
}

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

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

1
2
3
4
5
6
7
Got processing request - starting processing
Processing has started
Generating results
got results
Generating more results
got results
Processing has completed

相反,实际的输出是:

1
2
3
4
5
6
7
Got processing request - starting processing
Generating results
Generating more results
Processing has started
got results
got results
Processing has completed

应该表明处理已经开始的事件实际上发生在实际处理之后("生成结果")。

查看源代码后,我明白为什么会这样。这是 EventBus.

的相关源代码

1
2
3
4
5
6
7
8
9
10
11
12
  /**
   * 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 中读到了

The EventBus guarantees that it will not call a subscriber method from
multiple threads simultaneously, unless the method explicitly allows
it by bearing the @AllowConcurrentEvents annotation.

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

编辑

这里问题的原因是我 post 来自订阅者。由于事件总线不可重入,因此这些"子帖子"会排队并在第一个处理程序完成后处理。我可以注释掉 EventBus 源代码中的 if (isDispatching.get()) { return; } 部分,并且一切都按照我的预期运行 - 所以真正的问题是我这样做引入了哪些潜在问题?设计师们似乎做出了一个谨慎的决定,不允许重入。


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

如果在 t1 时刻发布了事件 E1,则通知所有订阅者。
如果其中一个订阅者在它的@Subscribe-method 中发布了一个事件本身(片刻之后),则"新"事件 E2 将被排入队列并在之后交付。之后的意思是:毕竟@Subscribe-methods for E1 from t1 确实返回了。

将这种"级联"事件发布与广度优先树遍历进行比较。

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


EventBus 通常运行的原则是,将事件发布到总线的代码不应该关心订阅者对事件做了什么或何时,除了尊重事件发布的顺序(在无论如何都是同步事件总线的情况)。

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


我知道这个问题已经有 4 年了,但我今天遇到了同样的问题。有一个简单的(和违反直觉的)改变来获得你想要的行为。根据 https://stackoverflow.com/a/53136251/1296767,您可以将 AsyncEventBus 与 DirectExecutor 一起使用:

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

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

1
2
3
4
5
6
7
Got processing request - starting processing
Processing has started
Generating results
got results
Generating more results
got results
Processing has completed

在通知所有"订阅者"之前,不会返回到 EventBus 的发布......这些订阅者可能还没有开始执行。这意味着当第一个 bus.post 返回时,您将继续下一个帖子,而没有任何介入的订阅者开始处理。

public void post(Object event) Posts an event to all registered
subscribers. This method will return successfully after the event has
been posted to all subscribers, and regardless of any exceptions
thrown by subscribers. If no subscribers have been subscribed for
event's class, and event is not already a DeadEvent, it will be
wrapped in a DeadEvent and reposted.

Parameters: event - event to post.