簡體   English   中英

使用ExecutorService時出現超時問題

[英]Issue with timeouts when using ExecutorService

我有幾十萬個Callable對象的列表。 每次運行時,它都會根據提供給它的值執行可能冗長的計算。 因此,我想異步運行每個任務(最好通過使用某種Executor),並在30秒后檢索每個計算的結果,以取消未及時完成的​​任務。 (獲得的值在其他地方使用。)

到目前為止,這是我的實現方式:

private void process() {
    class Runner implements Callable<BigDecimal> {
        final int x, y;

        Runner(int x, int y) {
            this.x = x;
            this.y = y;
        }

        @Override
        public BigDecimal call() {
            BigDecimal gWidth = xMax.subtract(xMin), gHeight = yMax.subtract(yMin);
            BigDecimal gy = gHeight.multiply(BigDecimal.valueOf(-y)).divide(BigDecimal.valueOf(height)).add(yMax);
            BigDecimal gx = gWidth.multiply(BigDecimal.valueOf(x)).divide(BigDecimal.valueOf(width)).add(xMin);

            // The calculation begins when this method is called
            BigDecimal result = calculateAt(gx, gy);
            return result;
        }
    }

    ExecutorService exec = Executors.newCachedThreadPool();
    List<Runner> runners = new ArrayList<>();
    for (int y = 0; y < height; y++) {
        for (int x = 0; x < width; x++) {
            runners.add(new Runner(x, y));
        }
    }

    try {
        List<Future<BigDecimal>> results = exec.invokeAll(runners, 30, TimeUnit.SECONDS);
        for (Future<BigDecimal> future : results) {
            // Check if the future's task was cancelled and process the results
        }
    } catch (InterruptedException | ExecutionException ex) {
        ex.printStackTrace();
    }
    exec.shutdown();
}

// Extra variables and methods
BigDecimal xMin = BigDecimal.valueOf(-7),
           xMax = BigDecimal.valueOf(7),
           yMin = BigDecimal.valueOf(-7),
           yMax = BigDecimal.valueOf(7);
int height = 850, width = 850;
private BigDecimal calculateAt(BigDecimal x, BigDecimal y) {
    try {
        // Just to simulate a potential execution time
        Thread.sleep((ThreadLocalRandom.current().nextInt(45) + 1) * 1000);
    } catch (InterruptedException ex) {
        ex.printStackTrace();
    }
    return BigDecimal.ONE;
}

ArrayList的runners存儲每個Callable執行,然后將其發送給ExecutorService的運行所有任務。 我遇到的問題是任務似乎是同步啟動的,並且在30秒超時后,只有前40或5萬個任務已經完成,而開始執行的任務就少得多。

似乎正在發生的事情是, ExecutorService.invokeAll方法僅允許30秒的窗口來啟動結束執行列表中的所有任務。 相反,我需要讓這30秒的窗口以每個任務為基礎開始,即在任務開始后等待30秒才能完成 invokeAll似乎沒有執行此操作,至少沒有使用newCachedThreadPool 是否有Java庫或其他實現方法?

最簡單的方法是分別調用每個任務,例如在初始化Runner的循環中。 由於您上面提到的行為與ExecutorServiceJavaDoc文檔兼容,因此沒有任何子類(至少是Java Standard Edition附帶的子類)會有所不同。 實現該行為的獨特功能也不存在。

#invokeAll(List<? extends Callable>, long, TimeUnit)的文檔中:

執行給定的任務,並在所有任務完成或超時到期時(以先發生者為准)返回持有其狀態和結果的期貨列表。 Future.isDone()對於返回列表的每個元素為true。 返回時,尚未完成的任務將被取消。 請注意,已完成的任務可能已正常終止,也可能引發異常。 如果在進行此操作時修改了給定的集合,則此方法的結果不確定。

我認為您可以使用CompletableFuture解決問題。

例如,以下是基於問題片段的代碼:

private static final ExecutorService executor = Executors.newCachedThreadPool();
private static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(100);

private static void processAsync() {
  List<CompletableFuture<Object>> futureList = IntStream.range(0, height).boxed()
    .flatMap(y -> IntStream.range(0, width).boxed().map(x -> new Runner(x, y)))
    .map(runner ->
      CompletableFuture.anyOf(
        CompletableFuture.supplyAsync(runner, executor),
        timeout(Duration.ofSeconds(30))
      ).exceptionally(throwable -> {
        // timeout is handled here
        return BigDecimal.ZERO;
      })
    )
    .collect(Collectors.toList());

  CompletableFuture.allOf(futureList.toArray(new CompletableFuture<?>[0]))
    .thenAccept(v -> {
      List<BigDecimal> results = futureList.stream()
        .map(CompletableFuture::join)
        .map(r -> (BigDecimal) r)
        .collect(Collectors.toList());
      // process final results
      BigDecimal sum = results.stream().reduce(BigDecimal.ZERO, BigDecimal::add);
      System.out.println("Final sum: " + sum);
    })
    .exceptionally(throwable -> {
      System.out.println("Failed allOf with " + throwable);
      return null;
    });
}

private static CompletableFuture<BigDecimal> timeout(Duration duration) {
  CompletableFuture<BigDecimal> future = new CompletableFuture<>();
  scheduler.schedule(
    () -> future.completeExceptionally(new TimeoutException("Timeout " + Thread.currentThread().getName())), duration.toMillis(), MILLISECONDS);
  return future;
}

private static class Runner implements Supplier<BigDecimal> {...

這里的主要思想是使用CompletableFuture.anyOf並將其應用於有用任務的CompletableFuture和超時任務的CopmpletableFuture 通過使用ScheduledExecutorServiceCompletableFuture.completeExceptionally實現超時。 因此,基本上, anyOf返回result或TimeoutException

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM