Координация выполнения асинхронных задач

Последнее обновление: 20.10.2025

Нередко требуется запустить несколько задач одновременно и получить результат первой успешно выполненной задачи или объединить результаты всех выполненных задач. Для этого класс ExecutorService предоставляет два метода: inVokeAll() и invokeAny()

invokeAll. Ожидание завершения всей группы задач

Метод invokeAll() отправляет на выполнение все задачи из коллекции объектов Callable, блокирует их выполнение до завершения всех задач и возвращает список объектов Future, которые представляют результаты всех задач, в том же порядке, в котором эти задачи были отправлены. Соответственно в соответствии с порядком можно совместить задачи и их результаты.

Недостатком данного метода в некоторых ситуациях может быть то, что он ждет завершения всех задач, даже если какие-то из них уже завершились с ошибкой.

Этот метод также имеет две версии:

<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)

Первая версия принимает коллекцию задач, которые надо выполнить, и возвращает список Future-объектов со статусом задач и их результатами.

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

Рассмотрим на примере вычисления факториалов в следующей программе:

import java.util.concurrent.*;
import java.util.List;
import java.util.ArrayList;

record FactorialResult(int number, int factorial){}

class FactorialTask implements Callable<FactorialResult>{

    private int number;
    FactorialTask(int number) {  this.number = number; }

    public FactorialResult call() throws Exception{

        if(number < 1) throw new Exception("Incorrect number: " + number + ". Number must be greater than 0");

        int result = 1;
        int n = number;
        while(n > 0)  result *= n--; 

        return new FactorialResult(number, result);  // возвращаем результат задачи
    };
}

class Program{

    public static void main(String[] args) {
         
        System.out.println("Main thread started...");


        try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {

            List<Callable<FactorialResult>> tasks = new ArrayList<Callable<FactorialResult>>();
            // добавляем несколько задач
            for(int i = -1; i < 4; i++) tasks.add(new FactorialTask(i));

            // вызываем и ждем завершения всех задач
            List<Future<FactorialResult>> results = executor.invokeAll(tasks);

            // получаем результаты
            for (Future<FactorialResult> result : results) {
                if (result.state() == Future.State.SUCCESS){
                    FactorialResult factorialResult =  result.resultNow();
                    System.out.printf("Factorial of %d:  %d\n", 
                            factorialResult.number(), factorialResult.factorial());
                }
                else{
                    var ex = result.exceptionNow();
                    System.out.println("Factorial exception: " + ex.getMessage());
                }
            }

        } // здесь вызывается метод executor.close() 
        catch(Exception ex){
            System.out.println(ex.getMessage());
        }
        System.out.println("Main thread finished...");
    }
}

Здесь для представления результата задачи определен класс FactorialResult:

record FactorialResult(int number, int factorial){}

Зднсь поле number представляет число, для которого вычисляется факториал, а поле factorial - сам вычисленный факториал числа.

Для представления задачи определен класс FactorialTask, который реализует интерфейс Callable - в методе call вычисляет факториал числа, послученного через конструктор, и возвращает результа - объект FactorialResult.

public FactorialResult call() throws Exception{

    if(number < 1) throw new Exception("Incorrect number: " + number + ". Number must be greater than 0");

    int result = 1;
    int n = number;
    while(n > 0)  result *= n--; 

    return new FactorialResult(number, result);  // возвращаем результат задачи
};

Допустим, нам надо вычислить факториалы от -1 до 4 (для теста выбираем ряд чисел заведо некорректных). Для этого создаем объект ExecutorService, который будет выполнять задачи на виртуальных потоках, и собственно список задач Callable:

try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {

    List<Callable<FactorialResult>> tasks = new ArrayList<Callable<FactorialResult>>();
    // добавляем несколько задач
    for(int i = -1; i < 4; i++) tasks.add(new FactorialTask(i));

Затем вызываем все задачи с помощью метода executor.invokeAll() и ждем их завершения:

List<Future<FactorialResult>> results = executor.invokeAll(tasks);

На этой строке метод main приостанавливает выполнение и ждет завершения всех задач. И когда от них станут доступны результаты, проходим в цикле по все результатам и обрабатываем их:

for (Future<FactorialResult> result : results) {
    if (result.state() == Future.State.SUCCESS){
        FactorialResult factorialResult =  result.resultNow();
        System.out.printf("Factorial of %d:  %d\n", 
        factorialResult.number(), factorialResult.factorial());
    }
    else{
        var ex = result.exceptionNow();
        System.out.println("Factorial exception: " + ex.getMessage());
    }
}

Каждый результат представляет объект Future, у которого с помощью метода state() можно проверить состояние выполнения: если задача выполнена успешно, то используем метод resultNow() для получения непосредственно самого результата в виде объекта FactorialResult. Если выполнение завершилось неудачно, то с помощью метода exceptionNow() получаем возникшее исключение и выводим его сообщение на консоль.

Консольный вывод программы:

Main thread started...
Factorial exception: Incorrect number: -1. Number must be greater than 0
Factorial exception: Incorrect number: 0. Number must be greater than 0
Factorial of 1:  1
Factorial of 2:  2
Factorial of 3:  6
Main thread finished...

invokeAny. Ожидание завершения одной задачи их группы

Метод invokeAny() отправляет на выполнение все задачи из коллекции объектов Callable и блокирует выполнение до тех пор, пока одна из них не завершится успешно, вернув свой результат. Этот метод имеет две версии:

invokeAny. Ожидание завершения одной задачи их группы

Метод invokeAny() отправляет на выполнение все задачи из коллекции объектов Callable и блокирует выполнение до тех пор, пока одна из них не завершится успешно, вернув свой результат. Этот метод имеет две версии:

<T> T invokeAny(Collection<? extends Callable<T>> tasks)
<T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)

Первая версия метода принимает коллекцию задач, из которых надо завершить одну. Результатом метода является результат первой завершенной задачи.

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

При этом все незавершенные задачи отменяются. Если ни одна задача не была выполнена успешно, генерируется исключение ExecutionException.

Изменим предыдущий код - пусть теперь хотя бы одна из задач по вычислению факториала будет выполнена:

import java.util.concurrent.*;
import java.util.List;
import java.util.ArrayList;

record FactorialResult(int number, int factorial){}
class FactorialTask implements Callable<FactorialResult>{

    private int number;
    FactorialTask(int number) {  this.number = number; }

    public FactorialResult call() throws Exception{

        if(number < 1) throw new Exception("Incorrect number: " + number + ". Number must be greater than 0");

        int result = 1;
        int n = number;
        while(n > 0)  result *= n--; 

        return new FactorialResult(number, result);  // возвращаем результат задачи
    };
}

class Program{

    public static void main(String[] args) {
         
        System.out.println("Main thread started...");


        try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
            List<Callable<FactorialResult>> tasks = new ArrayList<Callable<FactorialResult>>();
            // добавляем несколько задач
            for(int i = -1; i < 4; i++) tasks.add(new FactorialTask(i));

            // вызываем задачи и ждем, пока хотя бы одна из них не завершенится
            FactorialResult factorialResult = executor.invokeAny(tasks);
            System.out.printf("Factorial of %d:  %d\n", 
                    factorialResult.number(), factorialResult.factorial());

        }
        catch(Exception ex){
            System.out.println(ex.getMessage());
        }
        System.out.println("Main thread finished...");
    }
}

Большая часть логики здесь та же, что и в предыдущем примере. Отличие только в том что ждем выполнения одной задачи с помощью invokeAny():

FactorialResult factorialResult = executor.invokeAny(tasks);
System.out.printf("Factorial of %d:  %d\n",  factorialResult.number(), factorialResult.factorial());

И в данном случае вывод может быть следующим (консольный вывод не детерминирован):

Main thread started...
Factorial of 3:  6
Main thread finished...

Если при выполнении задачи возникнет ошибка, то будет сгенерировано исключение, которое будет обработано в блоке catch:

Main thread started...
java.lang.Exception: Incorrect number: -1. Number must be greater than 0
Main thread finished...
Помощь сайту
Юмани:
410011174743222
Номер карты:
4048415020898850
Morty Proxy This is a proxified and sanitized view of the page, visit original site.