#java #asynchronous #completable-future
Вопрос:
Я выполняю некоторую тяжелую работу следующим образом:
try {
Input in = initInput(param);
Result re = doHeavyWork(in);
saveResultSomewhereElse(re);
} catch (Exception e) {
logExecutionError(e);
}
Кроме того, иногда doHeavyWork
требуется слишком много времени, чтобы мы могли убить его вручную, чтобы сэкономить ресурсы. Тем не менее, я хотел бы поступить logExecutionError
в таком случае.
Преобразуя приведенный выше блок кода в CompletableFuture
, мы могли бы получить:
CompletableFuture.completedFuture(param)
.thenApply(initInput)
.thenApplyAsync(doHeavyWork, dedicatedExecutorService)
.thenApply(saveResultSomewhereElse)
.exceptionally(logExecutionError);
Мой вопрос заключается в том , что если я завершу dedicatedExecutorService
, будет ли действительно убита тяжелая работа и logExecutionError
гарантированно ли она будет выполнена?
Комментарии:
1. ты имеешь в виду, если ты позвонишь
dedicatedExecutorService.shutDownNow()
? Если да, то это тривиально выяснить?2. В общем,
CompletableFuture
есть методcancel
, но он ничего не «прерывает» (внимательно прочитайте его документацию). С другой стороныshutDownNow
, исполнитель будет прерывать рабочий поток, если такое прерывание поддерживается. Поэтому, если ваша логика вdoHeavyWork
опорах прерывается, вы можете вызвать этот метод; но это откроет новые проблемы.
Ответ №1:
Короткий ответ: нет. Несколько более длинный ответ заключается в том, что в Java нет хорошего способа завершить поток, если он не сотрудничает. Поток#stop не должен использоваться и недоступен в ExecutorService. Когда вы завершаете работу службы исполнителя, она может попросить задания остановиться, прервав их, но не может заставить их остановиться. Если doHeavyWork
обработка прерывается и останавливается, то она завершится изящно или с исключением (в зависимости от того, как вы ее спроектировали), и может быть продолжен следующий этап.
Честно говоря, я не уверен, что код thenApply
будет запущен, если служба исполнителя была остановлена. Лично я бы остановился и прервал doHeavyWork
, не останавливая бассейн. Тогда вы полностью контролируете ситуацию и можете оставить службу исполнителя для других задач.
Комментарии:
1. «остановитесь и прервите тяжелую работу» как именно вы планируете это сделать? работа выполняется в
CompletableFuture
рабочем потоке.2. А docs.oracle.com/javase/7/docs/api/java/util/concurrent/atomic/… для изящной остановки-хорошее начало. Если задание также необходимо прервать (например, для остановки ввода-вывода), оно может сохранить поток где-нибудь при его запуске, чтобы он был доступен, когда его необходимо прервать. Если doHeavyWork переключается между несколькими потоками внутри, а не только с одним, все становится действительно запутанным, но если он однопоточный, он может просто сохранить поток и предоставить метод, который можно вызвать извне.
3. Для начала OP использует службу ExecutorService. Ваше предложение состоит в том, чтобы использовать
AtomicBoolean
(avolatile
было бы более уместно, но хорошо) для сигнала «стоп». Итак, если есть 2 завершаемых варианта, которые используют один и тот же пул, как вы планируете различать потоки, которые должны останавливаться, и нет? Вам придется как-то привязать каждогоAtomicBoolean
к нити. Я хотел бы посмотреть, как это выполнимо. Также обратите внимание , что OP действительно хочет прерватьdoHeavyWork
, в то время как ваш флаг не является прерыванием. Это далеко не так просто, как предполагает этот ответ.4. В целом, то, чего хочет достичь ОП, на самом деле невозможно.
CompletableFuture
имеетcancel
, но это только установит результат этого будущего, основная задача все равно будет счастливо продолжать выполняться. Я, честно говоря, не знаю хорошего способа справиться с этим в целом. Я видел (на самом деле понятные) такие сценарии, в которых требуется «убивать» задачи, но я не знаю (хорошего) способа сделать это, если таковой вообще существует.5. @Eugene Я действительно написал, что короткий ответ-НЕТ? Общего решения не существует, но если doHeavyWork можно адаптировать, возможно, можно попросить его остановить или (в противном случае) прервать его. Чтобы прервать, нужно завладеть потоком, который заранее неизвестен, но который может быть раскрыт. Чтобы попросить его прекратить идею с AtomicBoolean, нужно было передать экземпляр, а затем изменить значение извне. Нельзя передать изменчивое логическое значение и позже изменить его. Однако можно было бы выставить сеттер для изменчивого логического значения.