タイムアウト付きのタスクを実行するメソッドがあります。 ExecutorServer.submit()を使用してFutureオブジェクトを取得してから、future.get()をタイムアウトで呼び出します。これは正常に機能していますが、私の質問は、タスクによってスローされる可能性のあるチェック済み例外を処理する最良の方法です。次のコードは機能し、チェックされた例外を保持しますが、メソッドシグネチャのチェックされた例外のリストが変更されると、非常に不器用で壊れやすくなります。
これを修正する方法に関する提案はありますか? Java 5をターゲットにする必要がありますが、Javaの新しいバージョンに適切なソリューションがあるかどうかも知りたいです。
public static byte[] doSomethingWithTimeout( int timeout ) throws ProcessExecutionException, InterruptedException, IOException, TimeoutException {
Callable<byte[]> callable = new Callable<byte[]>() {
public byte[] call() throws IOException, InterruptedException, ProcessExecutionException {
//Do some work that could throw one of these exceptions
return null;
}
};
try {
ExecutorService service = Executors.newSingleThreadExecutor();
try {
Future<byte[]> future = service.submit( callable );
return future.get( timeout, TimeUnit.MILLISECONDS );
} finally {
service.shutdown();
}
} catch( Throwable t ) { //Exception handling of nested exceptions is painfully clumsy in Java
if( t instanceof ExecutionException ) {
t = t.getCause();
}
if( t instanceof ProcessExecutionException ) {
throw (ProcessExecutionException)t;
} else if( t instanceof InterruptedException ) {
throw (InterruptedException)t;
} else if( t instanceof IOException ) {
throw (IOException)t;
} else if( t instanceof TimeoutException ) {
throw (TimeoutException)t;
} else if( t instanceof Error ) {
throw (Error)t;
} else if( t instanceof RuntimeException) {
throw (RuntimeException)t;
} else {
throw new RuntimeException( t );
}
}
}
===更新===
多くの人が、1)一般的な例外として再スローするか、2)未チェックの例外として再スローすることを推奨する回答を投稿しました。これらの例外タイプ(ProcessExecutionException、InterruptedException、IOException、TimeoutException)は重要であるため、これらのいずれも行いたくありません。これらはそれぞれ、処理された呼び出しによって異なる方法で処理されます。タイムアウト機能が必要ない場合は、メソッドにこれら4つの特定の例外タイプをスローさせます(タイムアウト例外を除く)。タイムアウト機能を追加すると、メソッドシグネチャが変更されて汎用の例外タイプがスローされるとは思わない。
私はこの問題を深く見てきましたが、それは混乱です。 Java 5でも、6または7でも簡単な答えはありません。あなたが指摘する不器用さ、冗長性、脆弱性に加えて、ソリューションには実際にExecutionException
getCause()
を呼び出すときに削除することには、実際に重要なスタックトレース情報のほとんどが含まれています。
つまり、提示したコードでメソッドを実行するスレッドのすべてのスタック情報はExcecutionExceptionのみにあり、Callableのcall()
で始まるフレームのみを対象とするネストされた原因にはありません。つまり、doSomethingWithTimeout
メソッドは、ここでスローしている例外のスタックトレースにも表示されません。 executorからは、具体化されていないスタックのみを取得します。これは、ExecutionException
が呼び出しスレッドで作成された唯一のものであるためです(FutureTask.get()
を参照)。
私が知っている唯一の解決策は複雑です。問題の多くは、Callable
-throws Exception
。 Callable
の新しいバリアントを定義して、次のように、スローする例外を正確に指定できます。
public interface Callable1<T,X extends Exception> extends Callable<T> {
@Override
T call() throws X;
}
これにより、呼び出し可能オブジェクトを実行するメソッドは、より正確なthrows
句を持つことができます。最大でN個の例外を含む署名をサポートする場合、残念ながらこのインターフェイスのN個のバリエーションが必要になります。
これで、JDK Executor
を囲むラッパーを作成できます。これは、拡張Callableを受け取り、グアバの CheckedFuture のような拡張Future
を返します。チェックされた例外タイプは、コンパイル時にExecutorService
の作成とタイプから、返されたFuture
sに伝播され、最終的にgetChecked
メソッドに送られます。未来。
これが、コンパイル時の型安全性を実現する方法です。これは、以下を呼び出すのではなく:
Future.get() throws InterruptedException, ExecutionException;
あなたは電話することができます:
CheckedFuture.getChecked() throws InterruptedException, ProcessExecutionException, IOException
したがって、アンラップの問題は回避されます。メソッドは必要なタイプの例外をすぐにスローし、それらはコンパイル時に利用可能およびチェックされます。
ただし、getChecked
内では、still上記の「欠落した原因」のラップ解除の問題を解決する必要があります。これは、(呼び出し元のスレッドの)現在のスタックを、スローされた例外のスタックにステッチすることで実行できます。これは、Javaでのスタックトレースの通常の使用を拡張するものです。単一のスタックが複数のスレッドにまたがるので、何が起こっているかがわかれば機能し、理解しやすくなります。
もう1つのオプションは、スローされているものと同じもののanother例外を作成し、元のものを新しいものの原因として設定することです。完全なスタックトレースを取得し、原因の関係はExecutionException
の動作と同じになりますが、適切なタイプの例外があります。ただし、リフレクションを使用する必要があります。たとえば、通常のパラメーターを持つコンストラクターを持たないオブジェクトに対しては、動作が保証されません。
この状況で私がすることは次のとおりです。これにより、次のことが実現します。
コード:
public <V> V waitForThingToComplete(Future<V> future) {
boolean interrupted = false;
try {
while (true) {
try {
return future.get();
} catch (InterruptedException e) {
interrupted = true;
}
}
} catch (ExecutionException e) {
final Throwable cause = e.getCause();
this.prependCurrentStackTrace(cause);
throw this.<RuntimeException>maskException(cause);
} catch (CancellationException e) {
throw new RuntimeException("operation was canceled", e);
} finally {
if (interrupted)
Thread.currentThread().interrupt();
}
}
// Prepend stack frames from the current thread onto exception trace
private void prependCurrentStackTrace(Throwable t) {
final StackTraceElement[] innerFrames = t.getStackTrace();
final StackTraceElement[] outerFrames = new Throwable().getStackTrace();
final StackTraceElement[] frames = new StackTraceElement[innerFrames.length + outerFrames.length];
System.arraycopy(innerFrames, 0, frames, 0, innerFrames.length);
frames[innerFrames.length] = new StackTraceElement(this.getClass().getName(),
"<placeholder>", "Changed Threads", -1);
for (int i = 1; i < outerFrames.length; i++)
frames[innerFrames.length + i] = outerFrames[i];
t.setStackTrace(frames);
}
// Checked exception masker
@SuppressWarnings("unchecked")
private <T extends Throwable> T maskException(Throwable t) throws T {
throw (T)t;
}
動作しているようです。
以下に、チェック済みおよびチェック済み例外に対する興味深い情報をいくつか示します。 Brian Goetzディスカッション および Eckelディスカッション からのチェック済み例外に対する引数。しかし、この book でJoshuaが議論しているチェック済み例外リファクタリングについて既に実装して考えているかどうかは知りませんでした。
Effective Java pearls、Checked例外を処理する好ましい方法の1つによれば、Checked例外をUn-Checked例外に変換することです。たとえば、
try{
obj.someAction()
}catch(CheckedException excep){
}
この実装を変更する
if(obj.canThisOperationBeperformed){
obj.someAction()
}else{
// Handle the required Exception.
}
あなたの問題に対する答えがないのではないかと思います。基本的に、現在のスレッドとは異なるスレッドでタスクを起動し、ExecutorServiceパターンを使用して、タスクがスローできるすべての例外と、一定時間後にそのタスクを中断するボーナスをキャッチします。あなたのアプローチは正しいものです:あなたは裸のRunnableでそれをすることができませんでした。
そして、この例外は、情報がないため、特定のタイプ(ProcessExecutionException、InterruptedException、またはIOException)で再度スローする必要があります。別のタイプの場合は、RuntimeExceptionとして再スローする必要があります(すべてのケースをカバーしていないため、これは最良のソリューションではありません)。
したがって、そこにはインピーダンスの不一致があります。一方ではThrowableであり、他方では既知の例外タイプです。あなたがそれを解決しなければならない唯一の解決策は、あなたがしたことをすることです:型をチェックし、キャストでもう一度投げます。書き方は異なりますが、最終的には同じように見えます...
これが私の答えです。このコードを考えてみましょう
public class Test {
public static class Task implements Callable<Void>{
@Override
public Void call() throws Exception {
throw new IOException();
}
}
public static class TaskExecutor {
private ExecutorService executor;
public TaskExecutor() {
this.executor = Executors.newSingleThreadExecutor();
}
public void executeTask(Task task) throws IOException, Throwable {
try {
this.executor.submit(task).get();
} catch (ExecutionException e) {
throw e.getCause();
}
}
}
public static void main(String[] args) {
try {
new TaskExecutor().executeTask(new Task());
} catch (IOException e) {
System.out.println("IOException");
} catch (Throwable e) {
System.out.println("Throwable");
}
}
}
IOExceptionが出力されます。 Throwableを力強く投げて捕まえるというマイナス面を伴う許容可能な解決策であり、最終的なキャッチを
} catch (Throwable e) { ... }
また、次の方法で別のチャンスがあります
public class Test {
public static class Task implements Callable<Void>{
private Future<Void> myFuture;
public void execute(ExecutorService executorService) {
this.myFuture = executorService.submit(this);
}
public void get() throws IOException, InterruptedException, Throwable {
if (this.myFuture != null) {
try {
this.myFuture.get();
} catch (InterruptedException e) {
throw e;
} catch (ExecutionException e) {
throw e.getCause();
}
} else {
throw new IllegalStateException("The task hasn't been executed yet");
}
}
@Override
public Void call() throws Exception {
throw new IOException();
}
}
public static void main(String[] args) {
try {
Task task = new Task();
task.execute(Executors.newSingleThreadExecutor());
task.get();
} catch (IOException e) {
System.out.println("IOException");
} catch (Throwable e) {
System.out.println("Throwable");
}
}
}
Java.util.concurrent.Future.get()
のjavadocは次のことを述べています。次に、ExecutionException(およびJava.util.concurrent.Future.get()
で宣言されたCancellationおよびInterrupted)メソッドをキャッチしないのはなぜですか?
...
スロー:CancellationException-計算がキャンセルされた場合
ExecutionException-計算が例外をスローした場合
InterruptedException-待機中に現在のスレッドが中断された場合
したがって、基本的に、呼び出し可能オブジェクト内で例外をスローし、ExecutionException
をキャッチするだけです。 ExecutionException.getCause()
は、 javadoc に記載されているように、呼び出し可能オブジェクトがスローした実際の例外を保持します。これにより、チェック済み例外宣言に関連するメソッドシグネチャの変更から保護されます。
ところで、Throwable
とRuntimeExceptions
もキャッチするため、Errors
をキャッチするべきではありません。 Exception
をキャッチすることは少し優れていますが、RuntimeExceptions
をキャッチするため、まだお勧めできません。
何かのようなもの:
try {
MyResult result = myFutureTask.get();
} catch (ExecutionException e) {
if (errorHandler != null) {
errorHandler.handleExecutionException(e);
}
logger.error(e);
} catch (CancellationException e) {
if (errorHandler != null) {
errorHandler.handleCancelationException(e);
}
logger.error(e);
} catch (InterruptedException e) {
if (errorHandler != null) {
errorHandler.handleInterruptedException(e);
}
logger.error(e);
}
なぜあなたがcatchとinstanceof
にif/elseブロックを持っているのか分かりません、あなたが望むことをすることができると思います:-
catch( ProcessExecutionException ex )
{
// handle ProcessExecutionException
}
catch( InterruptException ex )
{
// handler InterruptException*
}
考慮すべきことの1つは、混乱を減らすために、呼び出し可能メソッド内で例外をキャッチし、独自のドメイン/パッケージ固有の例外として再スローすることです。作成する必要のある例外の数は、呼び出しコードが例外にどのように応答するかに大きく依存します。