Java: установить время ожидания для определенного блока кода? - PullRequest
55 голосов
/ 19 апреля 2011

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

Ответы [ 11 ]

42 голосов
/ 27 февраля 2013

Вот самый простой способ сделать это:

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();
30 голосов
/ 04 октября 2013

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

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!
20 голосов
/ 19 апреля 2011

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

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

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

6 голосов
/ 19 апреля 2011

Если вы хотите использовать тестовый код, вы можете использовать атрибут 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();
}
3 голосов
/ 19 апреля 2011

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

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

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

3 голосов
/ 19 апреля 2011

РЕДАКТИРОВАТЬ: Питер Лори совершенно прав: это не так просто, как прерывание потока (мое первоначальное предложение), а Executors & Callables очень полезны ...

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

Вызываемые возвращают фьючерсы, с помощью которых вы можете указать время ожидания при попытке «получить» результат будущего.Примерно так:

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

См. Future.get, Executors, and 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

1 голос
/ 03 августа 2017

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

0 голосов
/ 19 мая 2019

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

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

0 голосов
/ 09 ноября 2018

Если вы хотите, чтобы метод 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, чтобы он трижды повторил метод, еслиисключение.

0 голосов
/ 01 декабря 2015

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

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 }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...