为什么在异常完成之前调用get()会等待异常执行? [英] Why calling get() before exceptional completion waits for exceptionally to execute?

查看:105
本文介绍了为什么在异常完成之前调用get()会等待异常执行?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在回答这个问题时,我注意到 CompletableFuture 的奇怪行为:如果你有一个 CompletableFuture cf 并用 cf.exceptionally(),调用 cf.get()似乎表现得很奇怪:




  • 如果在特殊完成之前调用它,它会在返回
  • $ b之前等待执行 exceptionally()块$ b
  • 否则,它会立即失败,抛出预期的 ExecutionException



我错过了什么或这是一个错误吗?我在Ubuntu 17.04上使用Oracle JDK 1.8.0_131。



以下代码说明了这种现象:

  public static void main(String [] args){
long start = System.currentTimeMillis();
final CompletableFuture< Object> future = CompletableFuture.supplyAsync(() - > {
sleep(1000);
抛出新的RuntimeException(First);
})。thenApply(Function.identity());

future.exceptionally(e - > {
sleep(1000);
logDuration(start,Exceptionally);
返回null;
});

final CompletableFuture< Void> futureA = CompletableFuture.runAsync(() - > {
try {
future.get();
} catch(例外e){
} finally {
logDuration(开始,A);
}
});

final CompletableFuture< Void> futureB = CompletableFuture.runAsync(() - > {
sleep(1100);
try {
future.get();
} catch(异常e){
} finally {
logDuration(start,B);
}
});

试试{
future.join();
} catch(例外e){
logDuration(start,Main);
}

futureA.join();
futureB.join();
}

private static void sleep(final int millis){
try {
Thread.sleep(millis);
} catch(InterruptedException e){
Thread.currentThread()。interrupt();
}
}

private static void logDuration(long start,String who){
System.out.println(who +waited for+(System。 currentTimeMillis() - start)+ms);
}

输出:

  B等待1347ms 
特别等待2230ms
主要等待2230ms
A等待2230ms

正如你所看到的, futureB 在调用 get之前会休眠一下( )根本没有阻止。但是, futureA 和主线程等待例外()才能完成。



请注意,如果删除 .thenApply(Function.identity()),则不会发生此行为。

解决方案

唤醒睡眠线程是一个依赖行为,必须像其他任何一样进行处理,它没有优先权。另一方面,轮询 CompletableFuture 的线程已经完成,不会被唤醒,不需要被唤醒,因此,无需竞争与其他相关行为。



使用以下程序

  public static void main(String [] args){
final CompletableFuture< Object> future = CompletableFuture.supplyAsync(() - > {
waitAndLog(Supplier,null,1000);
抛出新的RuntimeException(First);
})。thenApply(Function .identity());
long start = System.nanoTime();

CompletableFuture.runAsync(() - > waitAndLog(A,future,0));

LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(10));

future.exceptionally(e - > {
waitAndLog(Exceptionally,null,1000);
返回null;
});

CompletableFuture.runAsync(() - > waitAndLog(B,future,0));
CompletableFuture.runAsync(() - > waitAndLog(C,future,1100));

waitAndLog(Main,future,0);
ForkJoinPool.commonPool()。awaitQuiescence(10,TimeUnit.SECONDS);
}
private static void waitAndLog(String msg,CompletableFuture<?> primary,int sleep){
long nanoTime = System.nanoTime();
对象结果;
try {
if(sleep> 0)Thread.sleep(sleep);
result = primary!= null? primary.get():null;
} catch(InterruptedException | ExecutionException ex){
result = ex;
}
long millis = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - nanoTime);
System.out.println(msg +等待+ millis +ms+(结果!= null?,得到+结果:));
}

我明白了,



< pre class =lang-none prettyprint-override> 供应商等待993ms
等待993ms,得到java.util.concurrent.ExecutionException:java.lang.RuntimeException:First
等待1108ms,得到java.util.concurrent.ExecutionException:java.lang.RuntimeException:First
异常等待998ms
Main等待1983ms,得到了java.util.concurrent.ExecutionException:java。 lang.RuntimeException:第一个
B等待1984ms,得到java.util.concurrent.ExecutionException:java.lang.RuntimeException:First

在我的计算机上,建议在此特定情况下,依赖操作按计划顺序执行,<$ c首先是$ c> A 。请注意,我在安排异常之前插入了额外的等待时间,这将是下一个相关操作。由于 B 在后台线程中运行,因此它是否设法在 Main 线程之前安排自己是不确定的不。我们可以在执行订单之前插入另一个延迟。



由于 C 轮询已经完成的未来,它可以立即进行,因此其净等待时间接近明确指定的休眠时间。



必须强调的是,这只是特定情景的结果,取决于实施情况细节。依赖操作没有保证的执行顺序。正如您可能已经注意到的那样,没有 .thenApply(Function.identity())步骤,实现会运行不同的代码路径,从而导致相关操作的执行顺序不同。



依赖关系构成了一个树,实现必须以有效的方式遍历它,而不会有堆栈溢出的风险,因此它必须以某种方式压缩它并进行小的更改依赖树的形状可能会以非直观的方式影响结果的顺序。


While answering this question, I noticed a strange behaviour of CompletableFuture: if you have a CompletableFuture cf and chain a call with cf.exceptionally(), calling cf.get() appears to behave strangely:

  • if you call it before exceptional completion, it waits for the execution of the exceptionally() block before returning
  • otherwise, it fails immediately by throwing the expected ExecutionException

Am I missing something or is this a bug? I am using Oracle JDK 1.8.0_131 on Ubuntu 17.04.

The following code illustrates this phenomenon:

public static void main(String[] args) {
    long start = System.currentTimeMillis();
    final CompletableFuture<Object> future = CompletableFuture.supplyAsync(() -> {
        sleep(1000);
        throw new RuntimeException("First");
    }).thenApply(Function.identity());

    future.exceptionally(e -> {
        sleep(1000);
        logDuration(start, "Exceptionally");
        return null;
    });

    final CompletableFuture<Void> futureA = CompletableFuture.runAsync(() -> {
        try {
            future.get();
        } catch (Exception e) {
        } finally {
            logDuration(start, "A");
        }
    });

    final CompletableFuture<Void> futureB = CompletableFuture.runAsync(() -> {
        sleep(1100);
        try {
            future.get();
        } catch (Exception e) {
        } finally {
            logDuration(start, "B");
        }
    });

    try {
        future.join();
    } catch (Exception e) {
        logDuration(start, "Main");
    }

    futureA.join();
    futureB.join();
}

private static void sleep(final int millis) {
    try {
        Thread.sleep(millis);
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
}

private static void logDuration(long start, String who) {
    System.out.println(who + " waited for " + (System.currentTimeMillis() - start) + "ms");
}

Output:

B waited for 1347ms
Exceptionally waited for 2230ms
Main waited for 2230ms
A waited for 2230ms

As you can see, futureB which sleeps a bit before calling get() does not block at all. However, both futureA and the main thread wait for exceptionally() to complete.

Note that this behaviour does not occur if you remove the .thenApply(Function.identity()).

解决方案

Waking up a sleeping thread is a dependent action which has to be processed like any other and it has no precedence. On the other hand, a thread polling a CompletableFuture when it has been completed already will not be put to sleep, have no need to be woken up, hence, no need to compete with the other dependent actions.

With the following program

public static void main(String[] args) {
    final CompletableFuture<Object> future = CompletableFuture.supplyAsync(() -> {
        waitAndLog("Supplier", null, 1000);
        throw new RuntimeException("First");
    }).thenApply(Function.identity());
    long start = System.nanoTime();

    CompletableFuture.runAsync(() -> waitAndLog("A", future, 0));

    LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(10));

    future.exceptionally(e -> {
        waitAndLog("Exceptionally", null, 1000);
        return null;
    });

    CompletableFuture.runAsync(() -> waitAndLog("B", future, 0));
    CompletableFuture.runAsync(() -> waitAndLog("C", future, 1100));

    waitAndLog("Main", future, 0);
    ForkJoinPool.commonPool().awaitQuiescence(10, TimeUnit.SECONDS);
}
private static void waitAndLog(String msg, CompletableFuture<?> primary, int sleep) {
    long nanoTime = System.nanoTime();
    Object result;
    try {
        if(sleep>0) Thread.sleep(sleep);
        result = primary!=null? primary.get(): null;
    } catch (InterruptedException|ExecutionException ex) {
        result = ex;
    }
    long millis=TimeUnit.NANOSECONDS.toMillis(System.nanoTime()-nanoTime);
    System.out.println(msg+" waited for "+millis+"ms"+(result!=null? ", got "+result: ""));
}

I get,

Supplier waited for 993ms
A waited for 993ms, got java.util.concurrent.ExecutionException: java.lang.RuntimeException: First
C waited for 1108ms, got java.util.concurrent.ExecutionException: java.lang.RuntimeException: First
Exceptionally waited for 998ms
Main waited for 1983ms, got java.util.concurrent.ExecutionException: java.lang.RuntimeException: First
B waited for 1984ms, got java.util.concurrent.ExecutionException: java.lang.RuntimeException: First

on my machine, suggesting that in this specific case, the dependent actions were executed right in the order they were scheduled, A first. Note that I inserted extra waiting time before scheduling Exceptionally, which will be the next dependent action. Since B runs in a background thread, it is non-deterministic whether it manages to schedule itself before the Main thread or not. We could insert another delay before either to enforce an order.

Since C polls an already completed future, it can proceed immediately, so its net waiting time is close to the explicitly specified sleeping time.

It must be emphasized that this is only the result of a particular scenario, dependent on implementation details. There is no guaranteed execution order for dependent actions. As you might have noticed yourself, without the .thenApply(Function.identity()) step, the implementation runs a different code path resulting in a different execution order of the dependent actions.

The dependencies form a tree and the implementation has to traverse it in an efficient manner without risking a stack overflow, hence it has to flatten it in some way and small changes to the shape of the dependency tree may influence the resulting order in a non-intuitive way.

这篇关于为什么在异常完成之前调用get()会等待异常执行?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆