簡體   English   中英

具有默認值的Interrupt CompletableFuture

[英]Interrupt CompletableFuture with default value

假設我有3個服務。 首先我調用serviceA ,它返回一個CompletableFuture 之后,我將結果並行調用serviceBserviceCthenCompose() )。 得到所有結果后,我想將所有3個結果合並,然后將其返回給某個調用者。 在調用方中,我要等待整個過程的整個X毫秒,以便:

  • 如果在進行serviceA調用時中斷了該過程:拋出一些異常(因此是強制性的)
  • 如果在進行serviceBserviceC調用時中斷了該過程,請返回一些默認值(它們是可選的)。 這就是我嘗試使用CompletableFuturegetNow(fallback)方法的原因

請檢查下面的代碼段,如果我在serviceBserviceC調用中使用了較長的延遲,那么我總是會遇到TimeoutException 我怎樣才能做到這一點?

public CompletableFuture<Result> getFuture() {
    CompletableFuture<A> resultA = serviceA.call();
    CompletableFuture<B> resultB = resultA.thenCompose(a -> serviceB.call(a));
    CompletableFuture<C> resultC = resultA.thenCompose(a -> serviceC.call(a));
    return CompletableFuture.allOf(resultB, resultC)
            .thenApply(ignoredVoid -> combine(
                    resultA.join(),
                    resultB.getNow(fallbackB),
                    resultC.getNow(fallbackC));
}

public Result extractFuture(CompletableFuture<Result> future) {
    Result result;
    try {
        result = future.get(timeOut, MILLISECONDS);
    } catch (ExecutionException ex) {
        ...
    } catch (InterruptedException | TimeoutException ex) {
        // I always ends up here...
    }
    return result;
}

.allOf(resultB, resultC)返回的.allOf(resultB, resultC)僅在resultBresultC都完成resultC完成,因此,相關函數ignoredVoid -> combine(resultA.join(), resultB.getNow(fallbackB), resultC.getNow(fallbackC)僅在resultBresultC完成時提供評估,並且提供fallback根本不起作用。

在這些函數中,通常不可能對get()調用做出反應。 考慮到將來在不同的時間有不同的超時可以有任意數量的get()調用,這應該是顯而易見的,但是傳遞給thenApply的函數僅被評估一次。

處理getFuture()使用者指定的超時的唯一方法是將其更改為返回接收超時的函數:

interface FutureFunc<R> {
    R get(long time, TimeUnit u) throws ExecutionException;
}
public FutureFunc<Result> getFuture() {
    CompletableFuture<A> resultA = serviceA.call();
    CompletableFuture<B> resultB = resultA.thenCompose(a -> serviceB.call(a));
    CompletableFuture<C> resultC = resultA.thenCompose(a -> serviceC.call(a));
    CompletableFuture<Result> optimistic = CompletableFuture.allOf(resultB, resultC)
        .thenApply(ignoredVoid -> combine(resultA.join(), resultB.join(), resultC.join()));
    return (t,u) -> {
        try {
            return optimistic.get(t, u);
        } catch (InterruptedException | TimeoutException ex) {
            return combine(resultA.join(), resultB.getNow(fallbackB),
                                           resultC.getNow(fallbackC));
        }
    };
}

public Result extractFuture(FutureFunc<Result> future) {
    Result result;
    try {
        result = future.get(timeOut, MILLISECONDS);
    } catch (ExecutionException ex) {
        ...
    }
    return result;
}

現在,只要B或C尚未完成,就可以進行具有不同超時的不同呼叫,並可能具有不同的結果。 並不是說combine方法會產生一些歧義,這可能還需要一些時間。

您可以將功能更改為

return (t,u) -> {
    try {
        if(resultB.isDone() && resultC.isDone()) return optimistic.get();
        return optimistic.get(t, u);
    } catch (InterruptedException | TimeoutException ex) {
        return combine(resultA.join(), resultB.getNow(fallbackB),
                                       resultC.getNow(fallbackC));
    }
};

等待可能已經運行的combine 無論哪種情況,都無法保證結果在指定的時間內交付,即使使用B和C的后備值,也可能會執行combine ,而combine可能會花費任意時間。

如果您想要取消行為,即所有結果查詢都返回相同的結果,即使由於第一個查詢而使用回退值計算得出了結果,也可以使用

public FutureFunc<Result> getFuture() {
    CompletableFuture<A> resultA = serviceA.call();
    CompletableFuture<B> resultB = resultA.thenCompose(a -> serviceB.call(a));
    CompletableFuture<C> resultC = resultA.thenCompose(a -> serviceC.call(a));
    CompletableFuture<Void> bAndC = CompletableFuture.allOf(resultB, resultC);
    CompletableFuture<Result> result = bAndC
        .thenApply(ignoredVoid -> combine(resultA.join(), resultB.join(),
                                                          resultC.join()));
    return (t,u) -> {
        try {
            bAndC.get(t, u);
        } catch (InterruptedException|TimeoutException ex) {
            resultB.complete(fallbackB);
            resultC.complete(fallbackC);
        }
        try {
            return result.get();
        } catch (InterruptedException ex) {
            throw new ExecutionException(ex);
        }
    };
}

這樣,即使單個FutureFunc上的查詢基於第一個超時導致的后備值,也將始終返回相同的結果。 此變體還始終從超時中排除執行combine

當然,如果根本不打算使用不同的超時,則可以重構getFuture()以提前獲取所需的超時,例如作為參數。 這將大大簡化實施,並可能再次返回未來:

public CompletableFuture<Result> getFuture(long timeOut, TimeUnit u) {
    CompletableFuture<A> resultA = serviceA.call();
    CompletableFuture<B> resultB = resultA.thenCompose(a -> serviceB.call(a));
    CompletableFuture<C> resultC = resultA.thenCompose(a -> serviceC.call(a));
    ScheduledExecutorService e = Executors.newSingleThreadScheduledExecutor();
    e.schedule(() -> resultB.complete(fallbackB), timeOut, u);
    e.schedule(() -> resultC.complete(fallbackC), timeOut, u);
    CompletableFuture<Void> bAndC = CompletableFuture.allOf(resultB, resultC);
    bAndC.thenRun(e::shutdown);
    return bAndC.thenApply(ignoredVoid ->
                           combine(resultA.join(), resultB.join(), resultC.join()));
}

暫無
暫無

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

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