忽略重复触发的事件

时间:2011-05-11 19:04:19

标签: java events swing concurrency

我有一些代码响应PropertyChangeEvent。这个事件的问题在于它可以连续几次触发或根本不触发。无法预测此事件是仅触发一次还是多次,并且我无法控制此事件是如何触发的。我希望事件监听器只被触发一次。

我对这个问题的解决方案是使用我写的一个名为DelayedRunnable的类。我将监听器包装在DelayedRunnable中。首次触发事件时,监听器计划在一秒钟后执行 - 我任意决定的时间。事件的后续触发被忽略,直到一秒钟过去。以下是DelayedRunnable的代码:

public class DelayedRunnable implements Runnable {
    final Runnable                 runnable;
    final int                      delayAmount;
    final TimeUnit                 delayUnit;
    final ScheduledExecutorService service         = new ScheduledThreadPoolExecutor(1);
    final Runnable                 executeRunnable = new ExecuteRunnable();
    final AtomicBoolean            scheduled       = new AtomicBoolean(false);

    public DelayedRunnable(Runnable runnable, int delayAmount, TimeUnit delayUnit) {
        if (runnable == null)
            throw new IllegalArgumentException("runnable == null");
        this.runnable = runnable;
        this.delayAmount = delayAmount;
        this.delayUnit = delayUnit;
    }

    public void run() {
        if (!scheduled.compareAndSet(false, true))
            return;
        service.schedule(executeRunnable, delayAmount, delayUnit);
    }

    class ExecuteRunnable implements Runnable {
        public void run() {
            runnable.run();
            scheduled.set(false);
        }
    }
}

当我将侦听器包装在DelayedRunnable中时,它只被调用一次。 DelayedRunnable的一个附带好处是,侦听器代码不会在Swing EDT上执行,因为侦听器代码很昂贵。

但是我发现有时候调用者永远不会被调用,可能是由于DelayedRunnable中的并发问题。当我重新启动应用程序时,监听器再次神奇地工作。正如人们所预料的那样,由于并发编程的本质,我无法重现DelayedRunnable无法工作的情况。另外,一秒的时间限制完全是任意的。在某些计算机上,一秒钟可能还不够。在其他计算机上,一秒钟太长。没有简单的方法来设定时限。

我有两个问题:

  • 有没有人知道DelayedRunnable我无法看到的问题?

  • 处理这个问题比使用DelayedRunnable方法更优雅吗?

3 个答案:

答案 0 :(得分:2)

你应该注意的一件事是在美国东部时间之外做gui工作。因为实际 runnable是在一个新线程上运行的(在执行程序服务中),所以它不能直接执行任何gui工作。

你的一个可运行程序可能会被挂起,因此将来的任何执行都会被阻止。

答案 1 :(得分:0)

AWT事件侦听器将在其他侦听器之前执行,因此您可以先在那里过滤事件。有类似的东西:

something like Container c;
.
.
.
c.addAWTEventListener(new AWTEventListener()
{
    public void eventDispatched(AWTEvent e)
    {
       if(! e instanceof YOUR_EVENT_TYPE)
       return;
       //code to filter here
       e.consume() //to remove if not in time bounds   
    }
});

请参阅http://download.oracle.com/javase/1.4.2/docs/api/java/awt/event/AWTEventListener.html了解用法

答案 2 :(得分:0)

正如jtahlborn所提到的,gui工作应该在事件队列中完成。确保所有gui工作都包含在EventQueue.invokeAndWaitEventQueue.invokeLater

实际上,根据事件生成方式的性质,您可以完全使用EventQueue.invokeLater来避免延迟机制,因为要调用的runnable位于队列的末尾。如果在运行第一个回调之前将所有多个事件回调添加到队列,则可以确保使用invokeLater添加到队列的runnable将不会看到任何后续事件。然后,您可以使用类似于DelayedRunnable中已有的标志,以确保只为第一个事件添加runnable。