在java中创建自己的事件的正确方法是什么

时间:2013-12-04 14:23:43

标签: java multithreading events user-interface

我是学生,我和很少的朋友一起做项目。我的任务是制作类库。这个库中的类应该为我的朋友提供API,他必须使GUI成为应用程序的一部分。 GUI可以由任何工具包(Swing,JavaFX,SWT,AWT,所有应该工作,实际上,即使没有GUI也应该工作)。我需要制作等待数据从网络到达的类。我不知道数据何时到达,并且UI必须在等待期间响应,所以我把它放在不同的线程中。现在的问题是如何在数据到达时进行GUI响应。好吧,我认为这是异步事件,GUI应该注册事件处理程序,我应该在事件发生时调用这些方法。我提出了这个解决方案:

interface DataArrivedListener{
    void dataArrived(String data);
}

class Waiter{
    private DataArrivedListener dal;
    public void setDataArrivedListener(DataArrivedListener dal){
        this.dal = dal;
    }

    void someMethodThatWaitsForData(){
        // some code goes here

        data = bufRdr.readLine();

        //now goes important line:

        dal.dataArrived(data);

        // other code goes here

    }


}

我的问题是: 我应该用这样的东西替换“重要”的一行:

java.awt.EventQueue.invokeLater(new Runnable(){
    @Override
    public void run(){
        dal.dataArrived(data);
    }
});

或类似的东西:

javafx.Platform.runLater(new Runnable(){
    @Override
    public void run(){
        dal.dataArrived(data);
    }
});

或许我应该做一些完全不同的事情?

问题是我不确定哪种方法适用于任何类型的UI。如果是GUI,dataArrived()可能会对GUI进行更改,无论它是什么类型的GUI,都应该在屏幕上正确地绘制这些更改。我也认为如果我“稍后调用此代码”会更好,这样我的someMethodThatWaitsForData()方法可以触发事件并继续工作。

感谢您的帮助。

4 个答案:

答案 0 :(得分:2)

这是我之前写过的一篇Event Listener文章。本文解释了如何编写自己的事件监听器。

如果您希望您的库可以使用任何GUI,那么您想要编写自己的事件侦听器是正确的。

我对Swing最熟悉,所以是的,您将拥有如下所示的GUI代码:

button.addActionListener(new ActionListener(){
    @Override
    public void actionPerformed(ActionEvent event){
        dal.buttonPressed(data);
    }
});

答案 1 :(得分:1)

如果您希望它与使用的GUI完全无关,唯一真正的解决方案是让接收方在dataArrived处理它。由于每个工具包都有自己的实现,所以你真正能够使它与任何工具包一起使用就是忽略它。否则你最终会得到的是“支持的工具包”列表和每个工具包的案例。

如果你只想让dataArrivedsomeMethodThatWaitsForData开始执行,那么你可以自己创建自己的调度线程或每次创建一个新线程。

答案 2 :(得分:1)

如果你想真正独立于任何前端系统,我建议你创建两个线程。第一个是你的Waiter,它只会监听事件并将它们放入某种Queue中(参见“所有已知的实现类”部分)。第二个将在队列不为空时调用数据侦听器。

答案 3 :(得分:1)

自发明并发包以来,在后台调用Runnable的概念已被弃用。这在早些时候完成的主要原因是,GUI代码需要在不同的线程中执行,以保证它保持响应,即使主线程忙于进行一些计算,但实际的多线程仍然是在很早的时候。由此产生的invokeLater概念可行,但具有强大的创建开销。如果你经常需要做一些小事情,这尤其令人讨厌,但每次你需要创建一个全新的Runnable时,只需将该事件放入Swing线程。

更现代的方法应该使用线程安全列表,例如LinkedBlockingQueue。在这种情况下,任何线程都可以将事件抛入队列,而其他侦听器/ GUI事件处理程序可以异步地将它们取出,而无需同步或后台Runnables。

示例:

初始化一个新按钮,按下它后会进行一些繁重的计算。

在GUI线程中,单击按钮后会调用以下方法:

void onClick() {
  executor.submit(this.onClickAction);
}

其中executor是ExecutorService,onClickAction是Runnable。由于onClickAction是在Button创建期间提交过一次的Runnable,因此此处不会访问新内存。让我们看看这个Runnable实际上做了什么:

void run() {
  final MyData data = doSomeHeavyCalculation();
  dispatcher.dispatch(myListeners, data);
}

dispatcher在内部使用如上所述的LinkedBlockingQueue(Executor在内部使用一个以及btw),其中myListeners是一个固定(并发)的侦听器列表和要分派的Object的数据。在LinkedBlockingQueue上,有几个线程正在使用take()方法等待。现在,一个人在新事件中被唤醒并执行以下操作:

while (true) {
  nextEvent = eventQueue.take();
  for (EventTarget target : nextEvent.listeners) {
    target.update(nextEvent.data);
  }
}

所有这一切背后的一般思想是,一旦你为你的代码利用所有核心,另外你保持尽可能低的生成对象的数量(可能有一些更优化,这只是演示代码)。特别是您不需要从头开始为频繁事件实例化新的Runnables,这会带来一定的开销。缺点是使用这种GUI模型的代码需要处理多线程一直在发生的事实。使用Java提供给您的工具并不困难,但这是一种完全不同的设计代码的方式。