Подтвердить что ты не робот

Java: установить таймаут на определенный блок кода?

Можно ли заставить Java выкинуть исключение после того, как какой-то блок кода будет работать дольше, чем допустимо?

4b9b3361

Ответ 1

Да, но, как правило, очень плохая идея заставить другой поток прерывать произвольную строку кода. Вы бы сделали это, только если вы намереваетесь завершить процесс.

Что вы можете сделать, это использовать Thread.interrupt() для выполнения задачи через определенное время. Однако, если код не проверяет это, он не будет работать. ExecutorService может сделать это проще с Future.cancel(true)

Гораздо лучше, когда код сам определяет время и останавливается, когда это необходимо.

Ответ 2

Вот простейший способ, которым я это знаю:

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();

Ответ 3

Я скомпилировал некоторые другие ответы в один метод утилиты:

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!

Ответ 4

Если это тестовый код, который вы хотите использовать, вы можете использовать атрибут time:

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

Если это производственный код, нет простого механизма, и какое решение вы используете, зависит от того, можете ли вы изменить код для синхронизации или нет.

Если вы можете изменить время выполнения кода, то простой подход заключается в том, чтобы ваш timed-код помнил его время начала и периодически текущее время против этого. Например.

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();
}

Ответ 5

EDIT: Питер Лоури совершенно прав: это не так просто, как прерывание потока (мое первоначальное предложение), а исполнители и вызывающие вызовы очень полезны...

Вместо прерывания потоков вы можете установить переменную в Callable после достижения таймаута. Вызываемый должен проверять эту переменную в соответствующих точках выполнения задачи, чтобы знать, когда остановиться.

Callables возвращают фьючерсы, с помощью которых вы можете указать тайм-аут, когда пытаетесь "получить" будущий результат. Что-то вроде этого:

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

Ответ 6

Я могу предложить два варианта.

  • В рамках метода, предполагая, что он является циклом и не ждет внешнего события, добавьте локальное поле и каждый раз проверяйте время вокруг цикла.

    void method() {
        long endTimeMillis = System.currentTimeMillis() + 10000;
        while (true) {
            // method logic
            if (System.currentTimeMillis() > endTimeMillis) {
                // do some clean-up
                return;
            }
        }
    }
    
  • Запустите метод в потоке и подсчитайте счетчик до 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) {}
    }
    

Недостатком этого подхода является то, что метод() не может вернуть значение напрямую, он должен обновить поле экземпляра, чтобы вернуть его значение.

Ответ 7

Я создал очень простое решение без использования каких-либо фреймворков или 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-соединение зависает или полностью ломается. Это заставило всю систему спуститься. и мне нужен был способ обнаружить это и предотвратить его. Поэтому я создал это и использовал его. Хорошо работает.

Ответ 8

Существует хакерский способ сделать это.

Установите некоторое логическое поле, чтобы указать, была ли выполнена работа. Затем перед блоком кода установите таймер для запуска фрагмента кода после таймаута. Таймер проверяет, завершил ли блок кода выполнение, а если нет, выбросьте исключение. В противном случае он ничего не сделает.

Конец блока кода должен, конечно, установить для поля значение true, чтобы указать, что работа выполнена.

Ответ 10

Вместо того, чтобы иметь задачу в новом потоке и таймер в основном потоке, установите таймер в новом потоке и задачу в основном потоке:

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 }
}

Ответ 11

Если вы хотите способ 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);
   }
}

Если вы используете spring, вы можете аннотировать метод с помощью @Retryable чтобы он трижды @Retryable метод, если @Retryable исключение.

Ответ 12

Я столкнулся с подобной проблемой, когда моей задачей было отправить сообщение в 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. Это тип проблемы, которую можно избежать, а не решить. Как и в моем случае, я избежал этого, предоставив тайм-аут, который будет достаточен почти во всех случаях.