Завершите, за исключением среднего завершенного этапа

#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 (a volatile было бы более уместно, но хорошо) для сигнала «стоп». Итак, если есть 2 завершаемых варианта, которые используют один и тот же пул, как вы планируете различать потоки, которые должны останавливаться, и нет? Вам придется как-то привязать каждого AtomicBoolean к нити. Я хотел бы посмотреть, как это выполнимо. Также обратите внимание , что OP действительно хочет прервать doHeavyWork , в то время как ваш флаг не является прерыванием. Это далеко не так просто, как предполагает этот ответ.

4. В целом, то, чего хочет достичь ОП, на самом деле невозможно. CompletableFuture имеет cancel , но это только установит результат этого будущего, основная задача все равно будет счастливо продолжать выполняться. Я, честно говоря, не знаю хорошего способа справиться с этим в целом. Я видел (на самом деле понятные) такие сценарии, в которых требуется «убивать» задачи, но я не знаю (хорошего) способа сделать это, если таковой вообще существует.

5. @Eugene Я действительно написал, что короткий ответ-НЕТ? Общего решения не существует, но если doHeavyWork можно адаптировать, возможно, можно попросить его остановить или (в противном случае) прервать его. Чтобы прервать, нужно завладеть потоком, который заранее неизвестен, но который может быть раскрыт. Чтобы попросить его прекратить идею с AtomicBoolean, нужно было передать экземпляр, а затем изменить значение извне. Нельзя передать изменчивое логическое значение и позже изменить его. Однако можно было бы выставить сеттер для изменчивого логического значения.