@Async предотвращает продолжение потока, пока другой поток не закончил - PullRequest
10 голосов
/ 01 декабря 2010

У меня есть приложение, в котором определенное количество раз что-то нужно вычислить. Эта функция вычислений имеет аннотацию @Async (из Spring Framework), которая позволяет выполнять эти вычисления в 4 потоках. Проблема в том, что мне нужно около 40000 этих вычислений, и я хочу знать время между началом и концом всех вычислений, поэтому я вижу, сколько времени это до и после цикла for, который вызывает функции вычисления. Но теперь все вычисления помещаются в очередь, поэтому цикл for завершается немедленно, а время составляет около 1 секунды, а для завершения вычислений требуется несколько часов. Я попытался установить максимальный размер очереди около 100 (это также хорошо для уменьшения использования памяти), но это тоже не решение, так как я пропущу последние 100 вычислений в общем времени, которое это занимает. Есть ли способ приостановить выполнение кода сразу после цикла for, пока все потоки не закончат свою работу, но все еще смогут использовать аннотацию @Async?

Этот код иллюстрирует ту же проблему:

Выполнение класса:

public class Foo {
    public void executeBlaALotOfTimes() {
        long before = System.currentTimeMillis();

        for (int i = 0; i<40000; i++) {
            executeBla();
        }

        long after = System.currentTimeMillis(); 

        System.out.println("Time it took for a lot of bla to execute: " + (after - before) / 1000.0 + " seconds.");
    }
}

И класс, который выполняет вычисления:

@Service
public class Bar {
    @Async
    public void executeBla() {
        System.out.println("Bla!");
    }
}

Это приведет к следующему выводу (при условии, что код в Foo выполняется бесконечно быстро):

Time it took for a lot of bla to execute: 0.0 seconds.
Bla!
Bla!
Bla!
Bla!
.
.
.
etc

Ответы [ 2 ]

31 голосов
/ 01 декабря 2010

Если вам нужно дождаться окончания выполнения, то вы можете вернуть Future в качестве возвращаемого значения, например,

@Async
public Future<Void> executeBla() {
    System.out.println("Bla!");
    return new AsyncResult<Void>(null);
}

Это немного искусственно, так как фактическое значение не возвращается,но он все еще позволит вызывающему коду ждать завершения всех выполнений:

public void executeBlaALotOfTimes() {
    long before = System.currentTimeMillis();

    Collection<Future<Void>> futures = new ArrayList<Future<Void>>();

    for (int i = 0; i<40000; i++) {
        futures.add(executeBla());
    }

    for (Future<Void> future : futures) {
        future.get();
    }

    long after = System.currentTimeMillis(); 

    System.out.println("Time it took for a lot of bla to execute: " + (after - before) / 1000.0 + " seconds.");
}

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

1 голос
/ 18 июня 2017

Альтернативой является возврат ListenableFuture и использование CountDownLatch.

@Async
public ListenableFuture<Void> executeBla() {
    try {
        System.out.println("Bla!");
        return AsyncResult.forValue(null);
    } catch (Throwable t) {
        return AsyncResult.forExecutionException(t);
    }
}

Этот сценарий позволяет избежать явного вызова future.get() для каждого будущего. Вы достигаете этого, добавляя обратные вызовы успеха и неудачи, которые, в свою очередь, уменьшают значение CountDownLatch, созданное именно для этой цели.

public void executeBlaALotOfTimes() {
    long before = System.currentTimeMillis();

    int numExecutions = 40000;
    CountDownLatch countDownLatch = new CountDownLatch(numExecutions);

    for (int i = 0; i<numExecutions; i++) {
        ListenableFuture<Void> future = executeBla();
        future.addCallback(
            aVoid -> countDownLatch.countDown(), 
            throwable -> countDownLatch.countDown()
        );
    }

    try {
        countDownLatch.await();
    } catch (InterruptedException e) {
        // Handle exception
    } finally {
        long after = System.currentTimeMillis();
        System.out.println("Time it took for a lot of bla to execute: " + (after - before) / 1000.0 + " seconds.");
    }

}

...