48

タイムアウトのあるタスクを実行するメソッドがあります。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 つの特定の例外タイプをスローする必要があります (まあ、TimeoutException を除く)。タイムアウト機能を追加しても、メソッド シグネチャが変更されて一般的な例外タイプがスローされるとは思いません。

4

11 に答える 11

22

私はこの問題を詳しく調べましたが、混乱しています。Java 5にも、6または7にも簡単な答えはありません。あなたが指摘する不器用さ、冗長性、および脆弱性に加えて、あなたのソリューションには実際には、ExecutionException呼び出すときに取り除こうとしgetCause()ている .重要なスタック トレース情報です。

つまり、提示されたコードでメソッドを実行しているスレッドのすべてのスタック情報は、ExcecutionException のみにあり、ネストされた原因にはありません。これcall()は、Callable で始まるフレームのみをカバーします。つまり、doSomethingWithTimeoutここでスローしている例外のスタック トレースにメソッドが表示されることさえありません。Executor からは具現化されていないスタックのみを取得します。これはExecutionException、呼び出しスレッドで作成されたのは だけであるためです (「 」を参照FutureTask.get())。

私が知っている唯一の解決策は複雑です。問題の多くは、Callable-の自由な例外仕様に起因しthrows Exceptionます。Callable次のように、スローする例外を正確に指定する新しいバリアントを定義できます。

public interface Callable1<T,X extends Exception> extends Callable<T> {

    @Override
    T call() throws X; 
}

これにより、callable を実行するメソッドがより正確なthrows節を持つことができます。最大 N 個の例外を持つ署名をサポートしたい場合は、残念ながら、このインターフェイスの N 個のバリアントが必要になります。

Executorこれで、拡張された Callable を受け取り、 guava のCheckedFutureFutureのような拡張された を返すJDK のラッパーを作成できるようになりました。チェックされた例外タイプは、コンパイル時に の作成とタイプから返された に伝播され、将来のメソッドで終了します。ExecutorServiceFuturegetChecked

これが、コンパイル時のタイプ セーフをスレッド化する方法です。これは、以下を呼び出すのではなく、次のことを意味します。

Future.get() throws InterruptedException, ExecutionException;

あなたは呼び出すことができます:

CheckedFuture.getChecked() throws InterruptedException, ProcessExecutionException, IOException

したがって、ラップ解除の問題は回避されます。メソッドは必要なタイプの例外をすぐにスローし、コンパイル時に使用可能になり、チェックされます。

getCheckedただし、上記の「原因が見つからない」アンラップの問題を解決する必要がありますこれを行うには、(呼び出しスレッドの) 現在のスタックを、スローされた例外のスタックにステッチします。これは Java でのスタック トレースの通常の使用法を拡張したものです。単一のスタックが複数のスレッドにまたがって拡張されるためです。

別のオプションは、スローされたものと同じものの別の例外を作成し、元の例外を新しい例外の原因として設定することです。完全なスタック トレースが取得され、原因の関係はそれがどのように機能するかと同じになりExecutionExceptionますが、適切な種類の例外が発生します。ただし、リフレクションを使用する必要があり、通常のパラメーターを持つコンストラクターを持たないオブジェクトの場合など、動作が保証されていません。

于 2012-10-06T04:21:44.847 に答える
1

私はあなたの問題に対する答えがないのではないかと思います。基本的に、現在のスレッドとは異なるスレッドでタスクを起動し、ExecutorServiceパターンを使用して、タスクがスローする可能性のあるすべての例外に加えて、一定時間後にそのタスクを中断するというボーナスをキャッチしたいと考えています。あなたのアプローチは正しいものです:あなたは裸のRunnableでそれをすることができませんでした。

そして、この例外についての情報がない場合は、ProcessExecutionException、InterruptedException、またはIOExceptionという特定のタイプで再度スローする必要があります。別のタイプの場合は、RuntimeExceptionとして再スローする必要があります(すべてのケースをカバーしているわけではないため、これは最善の解決策ではありません)。

したがって、インピーダンスの不一致があります。一方はThrowableで、もう一方は既知の例外タイプです。あなたがそれを解決しなければならない唯一の解決策は、あなたがしたことをすることです:タイプをチェックして、キャストでそれを再び投げます。別の方法で書くこともできますが、最終的には同じように見えます...

于 2012-07-03T09:33:23.677 に答える
1

チェックされた例外とチェックされた例外に対するいくつかの興味深い情報があります。ブライアン・ゲッツの議論と、エッケルの議論からのチェックされた例外に対する反対の議論。しかし、あなたがすでにこの本でJoshuaによって議論されているチェックされた例外リファクタリングを実装し、考えているかどうかはわかりませんでした。

効果的なJavaパールによると、チェックされた例外を処理するための好ましい方法の1つは、チェックされた例外をチェックされていない例外に変えることです。たとえば、

try{
obj.someAction()
}catch(CheckedException excep){
}

この実装をに変更します

if(obj.canThisOperationBeperformed){
obj.someAction()
}else{
// Handle the required Exception.
}
于 2012-05-03T20:00:40.630 に答える
0

Throwable呼び出しクラスで、最後をキャッチします。例えば、

try{
    doSomethingWithTimeout(i);
}
catch(InterruptedException e){
    // do something
}
catch(IOException e){
    // do something
} 
catch(TimeoutException e){
    // do something
}
catch(ExecutionException e){
    // do something
}
catch(Throwable t){
    // do something
}

の内容は次のdoSomethingWithTimeout(int timeout)ようになります。

.
.
.
ExecutorService service = Executors.newSingleThreadExecutor();
try {
    Future<byte[]> future = service.submit( callable );
    return future.get( timeout, TimeUnit.MILLISECONDS );
} 
catch(Throwable t){
    throw t;
}
finally{
    service.shutdown();
}

メソッドのシグネチャは次のようになります。

doSomethingWithTimeout(int timeout) throws Throwable

于 2012-06-28T00:26:40.853 に答える
0

なぜあなたがキャッチに if/else ブロックを持っているのかinstanceof分かりません.

catch( ProcessExecutionException ex )
{
   // handle ProcessExecutionException
}
catch( InterruptException ex )
{
   // handler InterruptException*
}

混乱を減らすために考慮すべきことの 1 つは、呼び出し可能なメソッド内で例外をキャッチし、独自のドメイン/パッケージ固有の例外として再スローすることです。作成する必要がある例外の数は、呼び出し元のコードが例外にどのように応答するかによって大きく異なります。

于 2012-05-03T19:30:14.230 に答える
-1

これを行う別の方法は次のとおりですが、質問のように instanceof チェックを使用するよりも、これが不器用であるか壊れにくいとは確信していません。

public static byte[] doSomethingWithTimeout(int timeout)
        throws ProcessExecutionException, InterruptedException, IOException, TimeoutException {
    ....
    try {
        ....
        return future.get(1000, TimeUnit.MILLISECONDS);
        .....
    } catch (ExecutionException e) {

        try {
            throw e.getCause();
        } catch (IOException ioe) {
            throw ioe;
        } catch (InterruptedException ie) {
            throw ie;
        } catch (ProcessExecutionException pee) {
            throw pee;
        } catch (Throwable t) {
            //Unhandled exception from Callable endups here
        }

    } catch (TimeoutException e) {
        throw e;
    } catch.....
}
于 2012-06-30T20:27:17.070 に答える
-1

これをお勧めするわけではありませんが、これができる方法です。これはタイプ セーフであり、後で変更しようとする人は、おそらく不満を抱くでしょう。

public class ConsumerClass {

    public static byte[] doSomethingWithTimeout(int timeout)
            throws ProcessExecutionException, InterruptedException, IOException, TimeoutException {
        MyCallable callable = new MyCallable();
        ExecutorService service = Executors.newSingleThreadExecutor();
        try {
            Future<byte[]> future = service.submit(callable);
            return future.get(timeout, TimeUnit.MILLISECONDS);
        } catch (ExecutionException e) {
            throw callable.rethrow(e);
        } finally {
            service.shutdown();
        }
    }

}

// Need to subclass this new callable type to provide the Exception classes.
// This is where users of your API have to pay the price for type-safety.
public class MyCallable extends CallableWithExceptions<byte[], ProcessExecutionException, IOException> {

    public MyCallable() {
        super(ProcessExecutionException.class, IOException.class);
    }

    @Override
    public byte[] call() throws ProcessExecutionException, IOException {
        //Do some work that could throw one of these exceptions
        return null;
    }

}

// This is the generic implementation. You will need to do some more work
// if you want it to support a number of exception types other than two.
public abstract class CallableWithExceptions<V, E1 extends Exception, E2 extends Exception>
        implements Callable<V> {

    private Class<E1> e1;
    private Class<E2> e2;

    public CallableWithExceptions(Class<E1> e1, Class<E2> e2) {
        this.e1 = e1;
        this.e2 = e2;
    }

    public abstract V call() throws E1, E2;

    // This method always throws, but calling code can throw the result
    // from this method to avoid compiler errors.
    public RuntimeException rethrow(ExecutionException ee) throws E1, E2 {
        Throwable t = ee.getCause();

        if (e1.isInstance(t)) {
            throw e1.cast(t);
        } else if (e2.isInstance(t)) {
            throw e2.cast(t);
        } else if (t instanceof Error ) {
            throw (Error) t;
        } else if (t instanceof RuntimeException) {
            throw (RuntimeException) t;
        } else {
            throw new RuntimeException(t);
        }
    }

}
于 2012-07-01T16:08:54.843 に答える
-1

の javadoc に java.util.concurrent.Future.get()は次のように記載されています。それでは、ExecutionException (および で宣言された Cancellation と Interrupted java.util.concurrent.Future.get()) メソッドをキャッチしないのはなぜでしょうか?

...
スロー:

CancellationException - 計算がキャンセルされた場合

ExecutionException - 計算が例外をスローした場合

InterruptedException - 待機中に現在のスレッドが中断された場合

したがって、基本的には、callable 内で例外をスローし、単に catch することができますExecutionException。次に、 javadocExecutionException.getCause()に記載されているように、呼び出し可能オブジェクトがスローした実際の例外を保持します。このようにして、チェックされた例外宣言に関連するメソッド シグネチャの変更から保護されます。

ちなみに、これは と もキャッチするため、 を決してキャッチしThrowableないでください。キャッチは少し良くなりますが、それでもキャッチするのでお勧めしません。RuntimeExceptionsErrorsExceptionRuntimeExceptions

何かのようなもの:

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);
}
于 2012-05-04T08:55:03.610 に答える
-2

この問題を解決する 1 つの方法を見つけました。それが ExecutionException の場合は、 exception.getCause() を呼び出して元のものを取得できます。次に、何らかの種類のランタイム例外で例外をラップするか、(私にとって最善の方法は) プロジェクト lombok ( https:// projectlombok.org/ )。小さなコード例を示します。さらに、例外をスローする前にいくつかの instanceof チェックを追加して、これが予期したものであることを確認できます。

@SneakyThrows
public <T> T submitAndGet(Callable<T> task) {
    try {
        return executor.submit(task).get(5, TimeUnit.SECONDS);
    } catch (InterruptedException | ExecutionException | TimeoutException e) {
        throw e.getCause();
    }
}
于 2016-03-15T14:18:58.550 に答える