Рассылка Guava EventBus

Я использую 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.

  /**
   * 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; } if (isDispatching.get()) { return; } в источнике EventBus и все ведет себя так, как я ожидал, - так что реальный вопрос заключается в том, какие потенциальные проблемы я представил, сделав это? Похоже, что дизайнеры приняли добросовестное решение не допускать повторного участия.

Ответы

Ответ 1

EventBus обычно работает по принципу, что код, отправляющий событие на шину, не должен заботиться о том, что подписчики делают с событиями или когда соблюдается порядок, в котором были отправлены события (в случае синхронного события автобус в любом случае).

Если вы хотите, чтобы определенные методы вызывались в определенное время в ходе вашего метода, и вы хотите убедиться, что эти методы завершены до того, как ваш метод продолжит (как вам кажется в вашем примере), почему бы не вызвать эти методы напрямую? Когда вы используете шину событий, вы явно отделяете свой код от того, что именно происходит в ответ на данное событие. Это желательно во многих случаях и является основной причиной существования EventBus, но, похоже, это не совсем то, что вы хотите здесь.

Ответ 2

Я пытаюсь суммировать поведение доставки событий в Guava EventBus:

Если событие E1 отправлено в момент t1, все подписчики уведомляются. Если один из подписчиков отправляет в него событие в качестве метода @Subscribe-method (крошечный момент позже), "новое" событие E2 ставится в очередь и передается впоследствии. Впоследствии это означает: ведь @Subscribe-методы для E1 из t1 вернулись.

Сравните этот тип "каскадированного" события, отправляя его на ширину первого обхода дерева.

Кажется, это явно выбранный дизайн EventBus.

Ответ 3

Пока сообщение в EventBus не возвращается до тех пор, пока все "подписчики" не будут сигнализированы. Эти подписчики НЕ могли начать выполнение. Это означает, что когда первый bus.post возвращается, вы продолжаете следующий пост без участия любого промежуточного абонента.

public void post (Событие объекта) Отправляет событие всем зарегистрированным подписчикам. Этот метод будет успешно возвращен после того, как событие будет отправлено всем подписчикам, и независимо от каких-либо исключений, вызванных подписчиками. Если подписчики не подписались на класс событий, а событие еще не является DeadEvent, оно будет завершено в DeadEvent и отправлено обратно.

Параметры: событие - событие для публикации.