问题 组合guava eventbus和AWT Event线程处理的最佳方法


当你有一个异步事件总线和fire事件时,让我们说在UI中捕获的模型中你可能有以下问题:

已注册的处理程序在工作线程中执行,但所有UI swing更改都需要在AWT事件线程中执行。 这意味着您需要将所有处理程序clode包含在内 EventQueue.invokeLater(...)

这看起来像很多锅炉板代码。 我想知道是否有更智能的解决方案来解决这个问题。

guava事件总线的扩展如何标记在特殊线程中执行的处理程序?这可以用例如标注来标记,例如 @ExecuteWithinEDT

class EventBusChangeRecorder {
  @Subscribe @ExecuteWithinEDT void recordCustomerChange(ChangeEvent e) {
    recordChange(e.getChange());
  }
}

4684
2018-03-01 16:13


起源



答案:


使用异步事件总线注册的处理程序在提供的任何线程上执行 Executor 选择运行它们,不一定是工作线程。

我所做的是创建了一个实现 Executor 在事件队列线程上运行东西。这很简单:

public class EventQueueExecutor implements Executor {
  @Override public void execute(Runnable command) {
    EventQueue.invokeLater(command);
  }
}

然后你可以创建你的 EventBus 接着就,随即:

EventBus eventBus = new AsyncEventBus(new EventQueueExecutor());

然后所有处理程序将在事件队列线程上执行。

编辑:

转发事件的示例:

public class EventForwarder {
  private final EventBus uiEventBus;

  public EventForwarder(EventBus uiEventBus) {
    this.uiEventBus = uiEventBus;
  }

  // forward all events
  @Subscribe
  public void forwardEvent(Object event) {
    uiEventBus.post(event);
  }

  // or if you only want a specific type of event forwarded
  @Subscribe
  public void forwardEvent(UiEvent event) {
    uiEventBus.post(event);
  }
}

只需订阅您的主事件总线并将所有事件发布到主事件总线,但是将所有UI组件订阅到UI事件总线。


9
2018-03-01 18:45



你的解决方案很好,但它带来了另一个问题:现在所有事件都在AWT内调度。在现实世界的应用程序中,我可能希望在AWT(GUI部分)中调度一些订户,而其他订户(来自模型或其他模块)不应该在AWT内调度。否则整个异步事件总线就没有多大意义了。 - langm
@langm:一种选择是拥有多个 EventBuses ...在事件队列上调用处理程序而在事件队列中调用处理程序的程序。想要在事件队列上调用的处理程序订阅该事件总线。然后,您可以拥有一个处理程序或处理程序,它们订阅OTHER事件总线并通过将它们发布到事件队列总线来处理某些类型的事件。 - ColinD
这可能是一个解决方案。但是,我的意思是,我必须订阅两次:一旦用户界面,一旦另一条总线的UI总线......一个很好的功能将是一个转发功能,只是将所有事件全局转发到另一个总线。这有道理吗? - langm
@langm:您可以轻松地将所有事件转发到另一辆公交车。请记住,事件调度是通过类型完成的:如果事件处理程序订阅了Object事件,它将接收所有事件。因此,您可以通过这种方式将所有事件从一个总线转发到另一个总线。此外,如果您只希望特定类型的事件进入UI,则可以让所有这些事件实现标记接口,并仅将该接口类型的事件转发到UI。 - ColinD
@ColinD:要小心,因为 EventForwarder 订阅所有活动,不再有任何活动 DeadEvents。 - Drew


答案:


使用异步事件总线注册的处理程序在提供的任何线程上执行 Executor 选择运行它们,不一定是工作线程。

我所做的是创建了一个实现 Executor 在事件队列线程上运行东西。这很简单:

public class EventQueueExecutor implements Executor {
  @Override public void execute(Runnable command) {
    EventQueue.invokeLater(command);
  }
}

然后你可以创建你的 EventBus 接着就,随即:

EventBus eventBus = new AsyncEventBus(new EventQueueExecutor());

然后所有处理程序将在事件队列线程上执行。

编辑:

转发事件的示例:

public class EventForwarder {
  private final EventBus uiEventBus;

  public EventForwarder(EventBus uiEventBus) {
    this.uiEventBus = uiEventBus;
  }

  // forward all events
  @Subscribe
  public void forwardEvent(Object event) {
    uiEventBus.post(event);
  }

  // or if you only want a specific type of event forwarded
  @Subscribe
  public void forwardEvent(UiEvent event) {
    uiEventBus.post(event);
  }
}

只需订阅您的主事件总线并将所有事件发布到主事件总线,但是将所有UI组件订阅到UI事件总线。


9
2018-03-01 18:45



你的解决方案很好,但它带来了另一个问题:现在所有事件都在AWT内调度。在现实世界的应用程序中,我可能希望在AWT(GUI部分)中调度一些订户,而其他订户(来自模型或其他模块)不应该在AWT内调度。否则整个异步事件总线就没有多大意义了。 - langm
@langm:一种选择是拥有多个 EventBuses ...在事件队列上调用处理程序而在事件队列中调用处理程序的程序。想要在事件队列上调用的处理程序订阅该事件总线。然后,您可以拥有一个处理程序或处理程序,它们订阅OTHER事件总线并通过将它们发布到事件队列总线来处理某些类型的事件。 - ColinD
这可能是一个解决方案。但是,我的意思是,我必须订阅两次:一旦用户界面,一旦另一条总线的UI总线......一个很好的功能将是一个转发功能,只是将所有事件全局转发到另一个总线。这有道理吗? - langm
@langm:您可以轻松地将所有事件转发到另一辆公交车。请记住,事件调度是通过类型完成的:如果事件处理程序订阅了Object事件,它将接收所有事件。因此,您可以通过这种方式将所有事件从一个总线转发到另一个总线。此外,如果您只希望特定类型的事件进入UI,则可以让所有这些事件实现标记接口,并仅将该接口类型的事件转发到UI。 - ColinD
@ColinD:要小心,因为 EventForwarder 订阅所有活动,不再有任何活动 DeadEvents。 - Drew


您可以创建仅在AWT线程上调度的EventBus:

EventBus mybus = new AsyncEventBus("awt",
    new Executor() {
        public void execute (Runnable cmd) {
            if (EventQueue.isDispatchThread()) {
                cmd.run();
            } else {
                EventQueue.invokeLater(cmd);
            }
        }
    });

1
2018-03-01 18:45



嗨,就像我对上述帖子的其他评论一样:所有事件现在都在AWT中发送。我想要的是一种方式,以便每个订阅者可以决定是否在AWT中发送。否则,在现实世界的应用程序中,异步事件总线将毫无意义 - langm