番石榴事件巴士调度

2022-09-04 19:54:31

我正在使用番石榴的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

  /**
   * 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)

发生的事情是,由于我已经调度了顶级事件,因此其余事件只是被推送到队列的末尾。我希望这类似于.NET事件,其中调用事件直到所有处理程序完成才返回。InitiateProcessing

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

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

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

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

编辑

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


答案 1

EventBus通常基于这样的原则运行,即将事件发布到总线的代码不应该关心订阅者如何处理事件或何时处理,除了遵守事件发布的顺序(无论如何在同步事件总线的情况下)。

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


答案 2

我试图总结Guava的EventBus事件交付行为:

如果在时刻 t1 发布事件 E1,则会通知所有订阅者。如果其中一个订阅者在其@Subscribe方法中发布事件本身(片刻之后),则“新”事件 E2 将排队并在之后传递。之后意味着这里:毕竟从t1@Subscribe方法确实返回了E1

将这种“级联”事件发布与广度第一树遍历进行比较。

这似乎是EventBus的显式选择设计。


推荐