Java 5: java.util.concurrent.FutureTask - Семантика cancel () и done () - PullRequest
3 голосов
/ 05 августа 2009

В настоящее время я выискиваю неприятную ошибку в многопоточной среде с использованием FutureTasks и Executors. Основная идея состоит в том, чтобы фиксированное число потоков выполняло отдельные задачи FutureTasks, которые вычисляют результат, который должен отображаться в таблице (не говоря уже о аспекте GUI).

Я так долго смотрю на это, что начинаю сомневаться в моем здравом уме.

Рассмотрим этот кусок кода:

public class MyTask extends FutureTask<Result> {
   private String cellId;
   ...
   protected void done() {
      if (isCancelled()) return;
      try {
          Result r = get(); // should not wait, because we are done
          ... // some processing with r
          sendMessage(cellId, r);
      } catch (ExecutionException e) { // thrown from get
         ... 
      } catch (InterruptedException e) { // thrown from get
         ... 
      }
   }
   ...
}

Когда done() вызывается Исполнителем, обрабатывающим экземпляр MyTask, я проверяю, попал ли я туда, потому что задача была отменена. Если так, я пропускаю все оставшиеся действия, особенно я не звоню sendMessage().

Документация для FutureTask.done () гласит:

Защищенный метод вызывается при переходе этой задачи в состояние isDone (как обычно, так и через отмену). Реализация по умолчанию ничего не делает. Подклассы могут переопределять этот метод для вызова обратных вызовов завершения или для ведения учета. Обратите внимание, что вы можете запросить статус внутри реализации этого метода, чтобы определить, была ли эта задача отменена. ( Справочник по API )

Но из документации FutureTask я не получаю семантику во время выполнения done(). Что если я вначале сдаю проверку isCancelled(), но сразу после этого какой-то другой поток вызывает мой метод cancel()? Это заставит мою задачу передумать и с этого момента ответить isCancelled() == true

Если так, как я узнаю позже, отправлено ли сообщение? Глядя на isDone(), я просто скажу, что выполнение задачи завершено, но поскольку isCancelled() также было верным, я не мог знать, удалось ли отправить сообщение вовремя.

Может быть, это очевидно, но я сейчас не вижу этого.

Ответы [ 4 ]

4 голосов
/ 22 ноября 2009

FutureTask#done() вызывается не более одного раза для любого данного экземпляра и вызывается только по одной причине - run() завершено либо с ошибкой, либо без нее, либо cancel() выполнялось до любого из предыдущие события произошли. Запись о завершении любого из этих результатов фиксация . Причина, по которой FutureTask завершена, не может измениться, независимо от того, какие события происходят, по-видимому, «одновременно».

Следовательно, в пределах FutureTask#done() только один из isCancelled() или isDone() вернет истину тогда и навсегда. Трудно провести различие между isDone() сообщением об истине из-за ошибки или успешного завершения. Вы не можете переопределить set() или setException(Throwable) решительно, поскольку оба делегата внутреннему AQS решают, пытается ли 1021 * записать успешное получение значения или столкновение исключение должно придерживаться. Переопределение любого метода только дает вам знать, что он был вызван, но вы не можете наблюдать решение, принятое базовой реализацией. Если какое-либо событие происходит "слишком поздно" - скажем, после отмены - попытка записи значения или исключения будет проигнорирована.

Изучая реализацию, я вижу единственный способ отличить неотмененный успешный результат ошибки - прикусить пулю и вызвать get().

3 голосов
/ 06 августа 2009

Почему бы не отправить сообщение "за пределами" задачи, основываясь на результатах объекта Future , возвращенного ExecutorService ? Я использовал этот шаблон, и он, кажется, хорошо работает: отправьте кучу вызываемых задач через ExecutorService . Затем для каждой основной задачи отправьте дополнительную задачу, которая ожидает Future основной задачи и выполняет некоторое последующее действие (например, отправку сообщения), только если Future указывает, что основная задача выполнена успешно. С этим подходом нет догадок. Когда возвращается вызов Future .get () , вы гарантируете, что задача достигла состояния терминала, если вы не вызываете версию get это требует аргумента времени ожидания.

Если вы выберете этот подход, вам следует использовать два отдельных экземпляра ExecutorService : один для первичных задач и один для вторичных. Это для предотвращения тупиков. Вы не хотите, чтобы дополнительные задачи запускались и потенциально блокировали запуск основных задач, когда размер пула потоков ограничен.

Нет необходимости расширять FutureTask . Просто реализуйте свои задачи как Вызываемые объекты. Но если по какой-то причине вы хотите определить, была ли задача отменена из кода Callable , просто проверьте состояние прерывания потока с помощью Thread.interrupted () .

3 голосов
/ 05 августа 2009

Из API (выделено мое):

публичная логическая отмена (логическое mayInterruptIfRunning)

Описание скопировано с интерфейса: Будущее

Пытается отменить выполнение этого задания. Эта попытка не удастся, если задача уже выполнена , уже отменена или не может быть отменена по какой-либо другой причине.

Таким образом, FutureTask отрабатывает предположение, что вы не можете отменить задачу, когда она перешла на этап isDone.

0 голосов
/ 06 августа 2009

Я предлагаю написать небольшой тестовый пример, который позволит вам вызвать cancel(), пока ваш Future экземпляр висит в done() и посмотреть, что произойдет.

...