在EDT中运行代码的正确方法是什么?

时间:2010-11-16 15:49:28

标签: java swing event-dispatch-thread

我有一个Swing应用程序,它使用Java Thread来不断执行某些操作。此操作的结果更新了UI中图形的内容:

class ExampleThread {
    ... 
    public void run() {
        while (running) {
            // extract some information
            ...

            // show results in UI
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                     // use information to update a graph
                }
            });

            // sleep some seconds
            ...
        }
    }
    ...
}

我遇到的问题是EDT在其他操作中臃肿。在这种情况下,ExampleThread可以注册更新图表的各种Runnable实例。对于我的应用程序,由于图表将在显示结果之前多次更新,因此将浪费时间。我想要的是每个EDT周期最多运行一次//update a graph代码。

我的问题是:确保Runnable只运行一次的正确方法是什么?

3 个答案:

答案 0 :(得分:5)

原子脏标志应该可以解决问题。这具有额外的好处,即只在必要时才添加到EDT队列。大的假设 - 保存提取的信息的数据结构是线程安全的,但是对于上面的代码来说,这也必须是正确的。如果不是,您应该考虑另一个答案中指出的SwingWorker方法;您还可以与脏标志结合使用以防止冗余更新。

AtomicBoolean dirty = new AtomicBoolean(false);
while (running) {
        // extract some information
        ...

        if (!dirty.getAndSet(true)) {            
          SwingUtilities.invokeLater(new Runnable() {
              public void run() {
                if (dirty.getAndSet(false)) {
                   // use information to update a graph
                }
              }
          });
        }

        // sleep some seconds
        ...
    }

答案 1 :(得分:3)

我建议使用SwingWorker并利用发布方法将更新发布回EDT。然后,EDT将在下次安排时应用一个或多个更新。例如,假设您的计算结果为Integer个实例:

// Boolean flag to determine whether background thread should continue to run.
AtomicBoolean running = new AtomicBoolean(true);

new SwingWorker<Void, Integer>() {
  public Void doInBackground() {
    while (running.get()) {
      // Do calculation
      Integer result = doCalculation();

      publish(result);
    }
  }

  protected void process(List<Integer> chunks) {
    // Update UI on EDT with integer results.
  }
}

另一种方法是创建单个Runnable实例,每次调度时只会消耗Queue个结果。这类似于SwingWorker在封面下的操作方式,但为您提供了更多控制权,因为您可以控制Queue实现并避免永久保留SwingWorker个合并线程之一。

private final Queue<Integer> resultQueue = Collections.synchronizedList(new LinkedList<Integer>());
// Thread-safe queue to hold results.    

// Our re-usable Runnable to be run on the EDT.  Consumes all results from the result queue
// before releasing the lock, although could obviously change this to consume up to N results.
Runnable consumer = new Runnable() {
  public void run() {
    synchronized(resultQueue) {
      Integer result;

      while ((result = resultQueue.take()) != null) {
        // Update UI.
      }
    }
  }
}

...

// Business logic to run on background thread.  Conditionally schedules the EDT to run as required.
while (true) {
  Integer result = doCalculation();

  synchronized(resultQueue) {
    boolean scheduleEdt = resultQueue.isEmpty();
    // Queue was empty before this result is added so need to schedule the EDT to run again.

    resultQueue.add(result);
  }

  if (scheduleEdt) {
    SwingUtilities.invokeLater(consumer);
  } else {
    System.err.println("EDT already scheduled -> Avoiding unecessary reschedule.");
  }
}

答案 2 :(得分:0)

或者,您可能希望使用javax.swing.Timer