8

特定のタスクを最大時間で実行するメソッドを作成しようとしています。その時間内に終了しない場合は、あきらめる前に何度も再試行する必要があります。また、各試行の間に数秒待機する必要があります。これが私が思いついたものであり、私のアプローチについていくつかの批評が欲しいです。またはを使用してこれを行うためのより簡単な方法ですか、ScheduledExecutorServiceそれともこれを行う私の方法で十分ですか?

public static <T> T execute(Callable<T> task, int tries, int waitTimeSeconds, int timeout) 
    throws InterruptedException, TimeoutException, Exception {

    Exception lastThrown = null;
    for (int i = 0; i < tries; i++) {
        try {
            final Future<T> future = new FutureTask<T>(task);
            return future.get(timeout, TimeUnit.SECONDS);
        } catch (TimeoutException ex) {
            lastThrown = ex;
        } catch (ExecutionException ex) {
            lastThrown = (Exception) ex.getCause();
        }
        Thread.sleep(TimeUnit.SECONDS.toMillis(waitTimeSeconds));
    }
    if (lastThrown == null) {
        lastThrown = new TimeoutException("Reached max tries without being caused by some exception. " + task.getClass());
    }
    throw lastThrown;
}
4

2 に答える 2

5

私は思いますが、ネットワーク関連のタスクをスケジュールしている場合は、再試行するのではなく、最終的には並行して実行する必要があると思います。この他のアプローチについては後で説明します。

コードに関しては、タスクをエグゼキュータに渡すか、FutureTaskをスレッドに渡す必要があります。スレッドを生成したり、単独で実行したりすることはありません。エグゼキュータ(ExecutorServiceを参照)がある場合は、FutureTaskも必要ありません。スケジュールを設定して、呼び出し可能オブジェクトを取得するだけです。

したがって、ExecutorServiceがある場合は、次のように呼び出すことができます。

Future<T> future = yourExecutor.submit(task);

Future.get(timeout)はそのタイムアウトを待機し、タスクがまったく開始されていない場合でも、たとえばExecutorが他の作業を行うのにすでに忙しく、空きスレッドが見つからない場合でも、最終的にTimeoutExceptionで戻ります。したがって、タスクを実行する機会を与えることなく、5回試行して数秒待つことになります。これはあなたが期待するものかもしれないし、そうでないかもしれませんが、通常はそうではありません。多分あなたはそれにタイムアウトを与える前にそれが始まるのを待つべきです。

また、TimeoutExceptionがスローされた場合でも、Futureを明示的にキャンセルする必要があります。そうしないと、get with timeoutが失敗したときに停止するというドキュメントやコードがないため、実行を継続する可能性があります。

キャンセルしても、Callableが「正しく書かれていない」と、しばらくの間実行し続ける可能性があります。コードのこの部分ではそれについて何もできません。Javaで他のスレッドが行っていることを「本当に停止」できるスレッドはないことを覚えておいてください。これには正当な理由があります。

ただし、タスクは主にネットワークに関連していると思います。そのため、スレッドの中断に正しく反応するはずです。

私は通常、次のような状況で別の戦略を使用します。

  1. public static T execute(Callable task、int maxTries、int timeout)と書くので、タスク、最大試行回数(場合によっては1)、最大合計タイムアウト( "何度でも、最大10秒で回答が必要です試してみて、10秒か何もしない」)
  2. タスクの生成を開始し、それをエグゼキューターに渡してから、future.get(timeout / tries)を呼び出します。
  3. 結果を受け取ったら、返品してください。例外を受け取った場合は、再試行します(後述)
  4. ただし、タイムアウトが発生した場合は、将来をキャンセルせず、代わりにリストに保存します。
  5. 時間が経過したか、再試行が多すぎるかを確認します。その場合、リスト内のすべての先物をキャンセルして例外をスローし、nullを返します。
  6. それ以外の場合は、サイクルし、タスクを再度スケジュールします(最初のタスクと並行して)。
  7. ポイント2を参照
  8. 結果を受け取っていない場合は、リストで将来を確認します。おそらく、以前に生成されたタスクの1つがそれを実行できた可能性があります。

タスクを複数回実行できると仮定すると(私が思うに、そうでなければ再試行する方法はありません)、ネットワーク関連のものについては、このソリューションの方がうまく機能することがわかりました。

ネットワークが実際に非常に混雑していると仮定して、ネットワーク接続を要求し、それぞれ2秒で20回再試行します。ネットワークがビジーであるため、20回の再試行のいずれも2秒で接続を取得できません。ただし、40秒間の1回の実行で、データの接続と受信に成功する場合があります。これは、ネットが遅いときにページを強制的にf5を押すようなもので、ブラウザを最初から起動する必要があるため、何の役にも立ちません。

代わりに、私はさまざまな先物を実行し続けます。データを取得することができた最初の先物は結果を返し、他の先物は停止されます。最初のものがハングした場合、2番目のものが機能するか、3番目のものが機能する可能性があります。

ブラウザと比較すると、別のタブを開いて、最初のタブを閉じずにそこにページをロードしようとするようなものです。ネットが遅い場合、2番目のネットには時間がかかりますが、最初のネットは停止せず、最終的には適切にロードされます。代わりに最初のタブがハングした場合、2番目のタブは急速にロードされます。どちらを先にロードしても、もう一方のタブを閉じることができます。

于 2012-08-01T02:16:01.620 に答える
1

executeあなたが呼び出されたスレッドは、非常に長い間ブロックされます。これがあなたにとって正しいかどうかわからない。基本的に、これらのタイプのタスクにScheduledExecutorServiceは最適です。タスクをスケジュールし、タイミングを指定できます。ScheduledThreadPoolExecutorを見てください

于 2012-08-01T06:14:02.217 に答える