web-dev-qa-db-ja.com

Java:特定のコードブロックにタイムアウトを設定しますか?

コードのブロックが許容範囲を超えて実行された後、強制的にJavaに例外をスローさせることはできますか?

57
htf

はい。ただし、通常、コードのランダムな行で別のスレッドに強制的に割り込みをかけることは非常に悪い考えです。これは、プロセスをシャットダウンする場合にのみ行います。

できることは、一定時間後にタスクにThread.interrupt()を使用することです。ただし、コードがこれをチェックしない限り機能しません。 ExecutorServiceはFuture.cancel(true)でこれを簡単にすることができます

コードがそれ自体の時間を計り、必要なときに停止するのははるかに優れています。

20
Peter Lawrey

これが私が知っている最も簡単な方法です:

final Runnable stuffToDo = new Thread() {
  @Override 
  public void run() { 
    /* Do stuff here. */ 
  }
};

final ExecutorService executor = Executors.newSingleThreadExecutor();
final Future future = executor.submit(stuffToDo);
executor.shutdown(); // This does not cancel the already-scheduled task.

try { 
  future.get(5, TimeUnit.MINUTES); 
}
catch (InterruptedException ie) { 
  /* Handle the interruption. Or ignore it. */ 
}
catch (ExecutionException ee) { 
  /* Handle the error. Or ignore it. */ 
}
catch (TimeoutException te) { 
  /* Handle the timeout. Or ignore it. */ 
}
if (!executor.isTerminated())
    executor.shutdownNow(); // If you want to stop the code that hasn't finished.

または、TimeLimitedCodeBlockクラスを作成してこの機能をラップし、次のように必要な場所で使用できます。

new TimeLimitedCodeBlock(5, TimeUnit.MINUTES) { @Override public void codeBlock() {
    // Do stuff here.
}}.run();
43
user2116890

私は他の回答のいくつかを単一のユーティリティメソッドにコンパイルしました。

public class TimeLimitedCodeBlock {

  public static void runWithTimeout(final Runnable runnable, long timeout, TimeUnit timeUnit) throws Exception {
    runWithTimeout(new Callable<Object>() {
      @Override
      public Object call() throws Exception {
        runnable.run();
        return null;
      }
    }, timeout, timeUnit);
  }

  public static <T> T runWithTimeout(Callable<T> callable, long timeout, TimeUnit timeUnit) throws Exception {
    final ExecutorService executor = Executors.newSingleThreadExecutor();
    final Future<T> future = executor.submit(callable);
    executor.shutdown(); // This does not cancel the already-scheduled task.
    try {
      return future.get(timeout, timeUnit);
    }
    catch (TimeoutException e) {
      //remove this if you do not want to cancel the job in progress
      //or set the argument to 'false' if you do not want to interrupt the thread
      future.cancel(true);
      throw e;
    }
    catch (ExecutionException e) {
      //unwrap the root cause
      Throwable t = e.getCause();
      if (t instanceof Error) {
        throw (Error) t;
      } else if (t instanceof Exception) {
        throw (Exception) t;
      } else {
        throw new IllegalStateException(t);
      }
    }
  }

}

このユーティリティメソッドを使用したサンプルコード:

  public static void main(String[] args) throws Exception {
    final long startTime = System.currentTimeMillis();
    log(startTime, "calling runWithTimeout!");
    try {
      TimeLimitedCodeBlock.runWithTimeout(new Runnable() {
        @Override
        public void run() {
          try {
            log(startTime, "starting sleep!");
            Thread.sleep(10000);
            log(startTime, "woke up!");
          }
          catch (InterruptedException e) {
            log(startTime, "was interrupted!");
          }
        }
      }, 5, TimeUnit.SECONDS);
    }
    catch (TimeoutException e) {
      log(startTime, "got timeout!");
    }
    log(startTime, "end of main method!");
  }

  private static void log(long startTime, String msg) {
    long elapsedSeconds = (System.currentTimeMillis() - startTime);
    System.out.format("%1$5sms [%2$16s] %3$s\n", elapsedSeconds, Thread.currentThread().getName(), msg);
  }

私のマシンでサンプルコードを実行した結果:

    0ms [            main] calling runWithTimeout!
   13ms [ pool-1-thread-1] starting sleep!
 5015ms [            main] got timeout!
 5016ms [            main] end of main method!
 5015ms [ pool-1-thread-1] was interrupted!
32
Neeme Praks

計測したいテストコードの場合、time属性を使用できます。

@Test(timeout = 1000)  
public void shouldTakeASecondOrLess()
{
}

本番コードの場合、単純なメカニズムはありません。また、使用するソリューションは、コードをタイミングに合わせて変更できるかどうかによって異なります。

時間指定されているコードを変更できる場合、単純なアプローチは、時間設定されたコードに開始時刻を記憶させ、これに対して現在時刻を定期的に記憶させることです。例えば。

long startTime = System.currentTimeMillis();
// .. do stuff ..
long elapsed = System.currentTimeMillis()-startTime;
if (elapsed>timeout)
   throw new RuntimeException("tiomeout");

コード自体がタイムアウトをチェックできない場合、別のスレッドでコードを実行し、完了またはタイムアウトを待つことができます。

    Callable<ResultType> run = new Callable<ResultType>()
    {
        @Override
        public ResultType call() throws Exception
        {
            // your code to be timed
        }
    };

    RunnableFuture future = new FutureTask(run);
    ExecutorService service = Executors.newSingleThreadExecutor();
    service.execute(future);
    ResultType result = null;
    try
    {
        result = future.get(1, TimeUnit.SECONDS);    // wait 1 second
    }
    catch (TimeoutException ex)
    {
        // timed out. Try to stop the code if possible.
        future.cancel(true);
    }
    service.shutdown();
}
6
mdma

編集:ピーター・ローリーは完全に正しいです:スレッドを中断するほど簡単ではなく(私の元の提案)、Executors&Callablesは非常に便利です...

スレッドに割り込むのではなく、タイムアウトに達するとCallableに変数を設定できます。呼び出し可能オブジェクトは、タスク実行の適切な時点でこの変数をチェックして、いつ停止するかを知る必要があります。

呼び出し可能オブジェクトはFutureを返します。これにより、Futureの結果を「取得」しようとするときのタイムアウトを指定できます。このようなもの:

try {
   future.get(timeoutSeconds, TimeUnit.SECONDS)
} catch(InterruptedException e) {
   myCallable.setStopMeAtAppropriatePlace(true);
}

Future.get、Executors、およびCallableを参照してください...

https://docs.Oracle.com/javase/8/docs/api/Java/util/concurrent/Future.html#get-long-Java.util.concurrent.TimeUnit-

https://docs.Oracle.com/javase/8/docs/api/Java/util/concurrent/Callable.html

https://docs.Oracle.com/javase/8/docs/api/Java/util/concurrent/Executors.html#newFixedThreadPool%28int%29

3
laher

2つのオプションを提案できます。

  1. メソッド内で、外部イベントを待機せずにループしていると想定して、ローカルフィールドを追加し、ループのたびに時間をテストします。

    void method() {
        long endTimeMillis = System.currentTimeMillis() + 10000;
        while (true) {
            // method logic
            if (System.currentTimeMillis() > endTimeMillis) {
                // do some clean-up
                return;
            }
        }
    }
    
  2. スレッドでメソッドを実行し、呼び出し元を10秒にカウントします。

    Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                method();
            }
    });
    thread.start();
    long endTimeMillis = System.currentTimeMillis() + 10000;
    while (thread.isAlive()) {
        if (System.currentTimeMillis() > endTimeMillis) {
            // set an error flag
            break;
        }
        try {
            Thread.sleep(500);
        }
        catch (InterruptedException t) {}
    }
    

このアプローチの欠点は、method()が値を直接返すことができず、インスタンスフィールドを更新してその値を返す必要があることです。

3
Farzin Zaker

フレームワークやAPIを使用せずに、非常にシンプルなソリューションを作成しました。これは、よりエレガントで理解しやすいように見えます。クラスはTimeoutBlockと呼ばれます。

public class TimeoutBlock {

 private final long timeoutMilliSeconds;
    private long timeoutInteval=100;

    public TimeoutBlock(long timeoutMilliSeconds){
        this.timeoutMilliSeconds=timeoutMilliSeconds;
    }

    public void addBlock(Runnable runnable) throws Throwable{
        long collectIntervals=0;
        Thread timeoutWorker=new Thread(runnable);
        timeoutWorker.start();
        do{ 
            if(collectIntervals>=this.timeoutMilliSeconds){
                timeoutWorker.stop();
                throw new Exception("<<<<<<<<<<****>>>>>>>>>>> Timeout Block Execution Time Exceeded In "+timeoutMilliSeconds+" Milli Seconds. Thread Block Terminated.");
            }
            collectIntervals+=timeoutInteval;           
            Thread.sleep(timeoutInteval);

        }while(timeoutWorker.isAlive());
        System.out.println("<<<<<<<<<<####>>>>>>>>>>> Timeout Block Executed Within "+collectIntervals+" Milli Seconds.");
    }

    /**
     * @return the timeoutInteval
     */
    public long getTimeoutInteval() {
        return timeoutInteval;
    }

    /**
     * @param timeoutInteval the timeoutInteval to set
     */
    public void setTimeoutInteval(long timeoutInteval) {
        this.timeoutInteval = timeoutInteval;
    }
}

例:

try {
        TimeoutBlock timeoutBlock = new TimeoutBlock(10 * 60 * 1000);//set timeout in milliseconds
        Runnable block=new Runnable() {

            @Override
            public void run() {
                //TO DO write block of code to execute
            }
        };

        timeoutBlock.addBlock(block);// execute the runnable block 

    } catch (Throwable e) {
        //catch the exception here . Which is block didn't execute within the time limit
    }

FTPアカウントに接続しなければならなかったとき、これは非常に役に立ちました。次に、ダウンロードしてアップロードします。 FTP接続がハングするか、完全に切断される場合があります。これにより、システム全体がダウンしました。そして、私はそれを検出し、それが起こらないようにする方法が必要でした。だから私はこれを作成して使用しました。かなりうまくいきます。

タスクを新しいスレッドに入れてタイマーをメインスレッドに入れる代わりに、タイマーを新しいスレッドに入れてタスクをメインスレッドに入れます。

public static class TimeOut implements Runnable{
    public void run() {
        Thread.sleep(10000);
        if(taskComplete ==false) {
            System.out.println("Timed Out");
            return;
        }
        else {
            return;
        }
    }
}
public static boolean taskComplete = false;
public static void main(String[] args) {
    TimeOut timeOut = new TimeOut();
    Thread timeOutThread = new Thread(timeOut);
    timeOutThread.start();
    //task starts here
    //task completed
    taskComplete =true;
    while(true) {//do all other stuff }
}
0
Reuben Varma

それを行うにはハックな方法があります。

作業が完了したかどうかを示すブールフィールドを設定します。次に、コードブロックの前に、タイムアウト後にコードを実行するタイマーを設定します。タイマーは、コードブロックの実行が完了したかどうかを確認し、完了していない場合は例外をスローします。それ以外の場合は何もしません。

もちろん、コードブロックの最後でフィールドをtrueに設定して、作業が完了したことを示す必要があります。

0
HXCaine

特定のタイムアウト時間内にメッセージをSQSにプッシュすることがタスクの場合、同様の問題に直面しました。別のスレッドを介して実行し、タイムアウトを指定して将来のオブジェクトを待機するという単純なロジックを使用しました。これにより、タイムアウトの場合にTIMEOUT例外が発生します。

final Future<ISendMessageResult> future = 
timeoutHelperThreadPool.getExecutor().submit(() -> {
  return getQueueStore().sendMessage(request).get();
});
try {
  sendMessageResult = future.get(200, TimeUnit.MILLISECONDS);
  logger.info("SQS_Push_SUCCESSFUL");
  return true;

} catch (final TimeoutException e) {
  logger.error("SQS_Push_TIMEOUT_EXCEPTION");
}

ただし、別のスレッドで実行されているコードを停止できない場合があり、その場合は真のネガが発生します。

たとえば、私の場合、リクエストがSQSに到達し、メッセージがプッシュされている間に、コードロジックが指定されたタイムアウトになりました。現在、実際には私のメッセージはキューにプッシュされましたが、メインスレッドはTIMEOUT例外のために失敗したと想定していました。これは、解決するのではなく回避できるタイプの問題です。私の場合のように、ほとんどすべての場合に十分なタイムアウトを提供することで回避しました。

中断するコードがアプリケーション内にあり、API呼び出しのようなものではない場合は、単に使用できます

future.cancel(true)

ただし、Java docsは、実行がブロックされることを保証することを示していることを忘れないでください。

"このタスクの実行をキャンセルしようとします。タスクが既に完了している場合、すでにキャンセルされている場合、または他の理由でキャンセルできなかった場合、この試行は失敗します。 、cancelが呼び出されたときにこのタスクが開始されていない場合、このタスクは実行されません。タスクが既に開始されている場合、mayInterruptIfRunningパラメーターは、タスクを停止しようとしてこのタスクを実行するスレッドを中断するかどうかを決定します。」

0
P3A

CompletableFutureの方法が必要な場合は、次のようなメソッドを使用できます。

public MyResponseObject retrieveDataFromEndpoint() {

   CompletableFuture<MyResponseObject> endpointCall 
       = CompletableFuture.supplyAsync(() ->
             yourRestService.callEnpoint(withArg1, withArg2));

   try {
       return endpointCall.get(10, TimeUnit.MINUTES);
   } catch (TimeoutException 
               | InterruptedException 
               | ExecutionException e) {
       throw new RuntimeException("Unable to fetch data", e);
   }
}

スプリングを使用している場合は、例外がスローされた場合にメソッドを3回再試行するように、メソッドに@Retryable注釈を付けることができます。

0
Tom Chamberlain