Как правильно запустить код в EDT только один раз? - PullRequest
4 голосов
/ 16 ноября 2010

У меня есть приложение Swing, которое использует поток Java, чтобы постоянно выполнять некоторые операции.Результаты этой операции обновляют содержимое графика в пользовательском интерфейсе:

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 экземпляры, которые обновляют график.Для моего приложения это будет пустая трата времени, так как график будет обновляться несколько раз, прежде чем показывать результаты.Я хотел бы запускать код //update a graph не более одного раза за цикл EDT.

Мой вопрос: как правильно обеспечить выполнение Runnable только один раз?

Ответы [ 3 ]

5 голосов
/ 16 ноября 2010

Атомный грязный флаг должен сделать свое дело. Это дает дополнительное преимущество - добавление в очередь 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
        ...
    }
3 голосов
/ 16 ноября 2010

Я предлагаю использовать 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.");
  }
}
0 голосов
/ 16 ноября 2010

В качестве альтернативы вы можете захотеть использовать javax.swing.Timer.

...