Java: set timeout on a certain block of code?

前端 未结 11 925
终归单人心
终归单人心 2020-11-27 03:45

Is it possible to force Java to throw an Exception after some block of code runs longer than acceptable?

相关标签:
11条回答
  • 2020-11-27 04:12

    EDIT: Peter Lawrey is completely right: it's not as simple as interrupting a thread (my original suggestion), and Executors & Callables are very useful ...

    Rather than interrupting threads, you could set a variable on the Callable once the timeout is reached. The callable should check this variable at appropriate points in task execution, to know when to stop.

    Callables return Futures, with which you can specify a timeout when you try to 'get' the future's result. Something like this:

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

    See 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

    0 讨论(0)
  • 2020-11-27 04:12

    If you want a CompletableFuture way you could have a method like

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

    If you're using spring, you could annotate the method with a @Retryable so that it retries the method three times if an exception is thrown.

    0 讨论(0)
  • 2020-11-27 04:15

    Here's the simplest way that I know of to do this:

    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.
    

    Alternatively, you can create a TimeLimitedCodeBlock class to wrap this functionality, and then you can use it wherever you need it as follows:

    new TimeLimitedCodeBlock(5, TimeUnit.MINUTES) { @Override public void codeBlock() {
        // Do stuff here.
    }}.run();
    
    0 讨论(0)
  • 2020-11-27 04:16

    I can suggest two options.

    1. Within the method, assuming it is looping and not waiting for an external event, add a local field and test the time each time around the loop.

      void method() {
          long endTimeMillis = System.currentTimeMillis() + 10000;
          while (true) {
              // method logic
              if (System.currentTimeMillis() > endTimeMillis) {
                  // do some clean-up
                  return;
              }
          }
      }
      
    2. Run the method in a thread, and have the caller count to 10 seconds.

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

    The drawback to this approach is that method() cannot return a value directly, it must update an instance field to return its value.

    0 讨论(0)
  • 2020-11-27 04:19

    If it is test code you want to time, then you can use the time attribute:

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

    If it is production code, there is no simple mechanism, and which solution you use depends upon whether you can alter the code to be timed or not.

    If you can change the code being timed, then a simple approach is is to have your timed code remember it's start time, and periodically the current time against this. E.g.

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

    If the code itself cannot check for timeout, you can execute the code on another thread, and wait for completion, or timeout.

        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();
    }
    
    0 讨论(0)
提交回复
热议问题