私はCompletableFuture
をいじってみましたが、奇妙なことに気づきました。
String url = "http://google.com";
CompletableFuture<String> contentsCF = readPageCF(url);
CompletableFuture<List<String>> linksCF = contentsCF.thenApply(_4_CompletableFutures::getLinks);
linksCF.thenAccept(list -> {
assertThat(list, not(empty()));
});
linksCF.get();
thenAccept
呼び出しでアサーションが失敗した場合、例外は伝播されません。私はそれからもっとsomethingい何かを試しました:
linksCF.thenAccept(list -> {
String a = null;
System.out.println(a.toString());
});
何も起こらず、例外は伝播されません。 handle
などの例外に関連するCompletableFutures
などのメソッドを使用しようとしましたが、失敗しました-期待どおりに例外が伝播していません。
CompletableFuture
をデバッグすると、次のように例外をキャッチします。
final void internalComplete(T v, Throwable ex) {
if (result == null)
UNSAFE.compareAndSwapObject
(this, RESULT, null,
(ex == null) ? (v == null) ? NIL : v :
new AltResult((ex instanceof CompletionException) ? ex :
new CompletionException(ex)));
postComplete(); // help out even if not triggered
}
何もありません。
私はJDK 1.8.0_05 x64、Windows 7を使用しています。
ここに何かが足りませんか?
問題は、linksCF.thenAccept(..)
への呼び出しの結果の受信を決して要求しないことです。
linksCF.get()
への呼び出しは、チェーン内の実行結果を待ちます。ただし、その後、CFの結果を返すだけです。これには、アサーションの結果は含まれません。
linksCF.thenAccept(..)
は、新しいCompletableFutureインスタンスを返します。スローされた例外を取得するには、get()
を呼び出すか、新しく返されるCompletableFutureインスタンスでisCompletedExceptionally()
を使用して例外ステータスを確認します。
CompletableFuture<Void> acceptedCF = linksCF.thenAccept(list -> {
assertThat(list, not(empty()));
});
acceptedCF.exceptionally(th -> {
// will be executed when there is an exception.
System.out.println(th);
return null;
});
acceptedCF.get(); // will throw ExecutionException once results are available
代替案?
CompletableFuture<List<String>> appliedCF = linksCF.thenApply(list -> {
assertThat(list, not(empty()));
return list;
});
appliedCF.exceptionally(th -> {
// will be executed when there is an exception.
System.out.println(th);
return Coolections.emptyList();
});
appliedCF.get(); // will throw ExecutionException once results are available
質問は基本的にGregor Koukkoullis(+1)によって既に回答されていますが、ここに [〜#〜] mcve [〜#〜] があり、これをテストするために作成しました。
内部的に問題を引き起こした実際の例外を取得するには、いくつかのオプションがあります。ただし、get
によって返されるfutureでthenAccept
を呼び出すことが問題になる理由はわかりません。疑わしいのは、thenApply
を恒等関数で使用し、次のようなNice fluentパターンを使用することもできます。
List<String> list =
readPage().
thenApply(CompletableFutureTest::getLinks).
thenApply(t -> {
// check assertion here
return t;
}).get();
しかし、おそらくこれを避けたい特定の理由があります。
import Java.util.ArrayList;
import Java.util.List;
import Java.util.concurrent.CompletableFuture;
import Java.util.concurrent.ExecutionException;
import Java.util.function.Supplier;
public class CompletableFutureTest
{
public static void main(String[] args)
throws InterruptedException, ExecutionException
{
CompletableFuture<String> contentsCF = readPage();
CompletableFuture<List<String>> linksCF =
contentsCF.thenApply(CompletableFutureTest::getLinks);
CompletableFuture<Void> completionStage = linksCF.thenAccept(list ->
{
String a = null;
System.out.println(a.toString());
});
// This will NOT cause an exception to be thrown, because
// the part that was passed to "thenAccept" will NOT be
// evaluated (it will be executed, but the exception will
// not show up)
List<String> result = linksCF.get();
System.out.println("Got "+result);
// This will cause the exception to be thrown and
// wrapped into an ExecutionException. The cause
// of this ExecutionException can be obtained:
try
{
completionStage.get();
}
catch (ExecutionException e)
{
System.out.println("Caught "+e);
Throwable cause = e.getCause();
System.out.println("cause: "+cause);
}
// Alternatively, the exception may be handled by
// the future directly:
completionStage.exceptionally(e ->
{
System.out.println("Future exceptionally finished: "+e);
return null;
});
try
{
completionStage.get();
}
catch (Throwable t)
{
System.out.println("Already handled by the future "+t);
}
}
private static List<String> getLinks(String s)
{
System.out.println("Getting links...");
List<String> links = new ArrayList<String>();
for (int i=0; i<10; i++)
{
links.add("link"+i);
}
dummySleep(1000);
return links;
}
private static CompletableFuture<String> readPage()
{
return CompletableFuture.supplyAsync(new Supplier<String>()
{
@Override
public String get()
{
System.out.println("Getting page...");
dummySleep(1000);
return "page";
}
});
}
private static void dummySleep(int ms)
{
try
{
Thread.sleep(ms);
}
catch (InterruptedException e)
{
e.printStackTrace();
Thread.currentThread().interrupt();
}
}
}
ThenAccept呼び出しでアサーションが失敗した場合、例外は伝達されません。
thenAccept()
で登録する継続は、linksCF
futureとは別のタスクです。 linksCF
タスクは正常に完了しました。報告するエラーはありません。最終的な価値があります。 linksCF
によってスローされる例外は、linksCF
の結果を生成する問題のみを示す必要があります。結果を消費する他のコードがスローされる場合、結果の生成に失敗したことを示していません。
継続で発生する例外を監視するには、継続のCompletableFuture
を監視する必要があります。
正しい。しかし、1)get()の呼び出しを強制されるべきではありません-新しい構成要素のポイントの1つ。 2)ExecutionExceptionにラップされている
thenAccept()
を使用して、複数の独立した継続に結果を引き渡したい場合はどうなりますか?これらの継続の1つがスローされた場合、なぜそれが親または他の継続に影響を与えるのでしょうか?
linksCF
をチェーン内のノードとして扱い、チェーン内で発生する結果(および例外)を観察する場合は、チェーン内の最後のリンクでget()
を呼び出す必要があります。
join()
の代わりにget()
を使用すると、チェックされていないExecutionException
を回避できます。これにより、エラーは未チェックのCompletionException
にラップされます(ただし、ラップされます)。
ここでの回答は"exceptionnaly"メソッドを使用してCompletableFutureの例外を管理するのに役立ちましたが、基本的な例を逃したため、Marco13の回答からヒントを得たものを次に示します。
/**
* Make a future launch an exception in the accept.
*
* This will simulate:
* - a readPage service called asynchronously that return a String after 1 second
* - a call to that service that uses the result then throw (eventually) an exception, to be processed by the exceptionnaly method.
*
*/
public class CompletableFutureTest2
{
public static void main(String[] args)
throws InterruptedException, ExecutionException
{
CompletableFuture<String> future = readPage();
CompletableFuture<Void> future2 = future.thenAccept(page->{
System.out.println(page);
throw new IllegalArgumentException("unexpected exception");
});
future2.exceptionally(e->{
e.printStackTrace(System.err);
return null;
});
}
private static CompletableFuture<String> readPage()
{
CompletableFuture<String> future = new CompletableFuture<>();
new Thread(()->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
// FUTURE: normal process
future.complete("page");
}).start();
return future;
}
}
避けるべき間違いは、例外をスローする可能性のあるラムダ(コード内の変数future2)を含む "thenAccept"によって返される未来の代わりに、1番目の未来(私のコード内の変数future)で "exceptionnaly"を呼び出すことです。 。
いつものように、CompletableFuture
の動作を理解することは、公式ドキュメントとブログに任せたほうがよいでしょう。
CompletionStage を実装するCompletableFuture
クラスの各then...()
チェーンメソッドは、引数CompletionStage
を受け入れます。渡されるステージは、チェーンしたthen...()
メソッドの順序によって異なります。繰り返しになりますが、これは 前述のブログ です。