How to timeout a thread

后端 未结 17 1115
时光说笑
时光说笑 2020-11-22 01:01

I want to run a thread for some fixed amount of time. If it is not completed within that time, I want to either kill it, throw some exception, or handle it in some way. How

相关标签:
17条回答
  • 2020-11-22 01:30

    I post you a piece of code which show a way how to solve the problem. As exemple I'm reading a file. You could use this method for another operation, but you need to implements the kill() method so that the main operation will be interrupted.

    hope it helps

    
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileNotFoundException;
    import java.io.IOException;
    import java.io.InputStream;
    
    /**
     * Main class
     * 
     * @author el
     * 
     */
    public class Main {
        /**
         * Thread which perform the task which should be timed out.
         * 
         * @author el
         * 
         */
        public static class MainThread extends Thread {
            /**
             * For example reading a file. File to read.
             */
            final private File fileToRead;
            /**
             * InputStream from the file.
             */
            final private InputStream myInputStream;
            /**
             * Thread for timeout.
             */
            final private TimeOutThread timeOutThread;
    
            /**
             * true if the thread has not ended.
             */
            boolean isRunning = true;
    
            /**
             * true if all tasks where done.
             */
            boolean everythingDone = false;
    
            /**
             * if every thing could not be done, an {@link Exception} may have
             * Happens.
             */
            Throwable endedWithException = null;
    
            /**
             * Constructor.
             * 
             * @param file
             * @throws FileNotFoundException
             */
            MainThread(File file) throws FileNotFoundException {
                setDaemon(false);
                fileToRead = file;
                // open the file stream.
                myInputStream = new FileInputStream(fileToRead);
                // Instantiate the timeout thread.
                timeOutThread = new TimeOutThread(10000, this);
            }
    
            /**
             * Used by the {@link TimeOutThread}.
             */
            public void kill() {
                if (isRunning) {
                    isRunning = false;
                    if (myInputStream != null) {
                        try {
                            // close the stream, it may be the problem.
                            myInputStream.close();
                        } catch (IOException e) {
                            // Not interesting
                            System.out.println(e.toString());
                        }
                    }
                    synchronized (this) {
                        notify();
                    }
                }
            }
    
            /**
             * The task which should be timed out.
             */
            @Override
            public void run() {
                timeOutThread.start();
                int bytes = 0;
                try {
                    // do something
                    while (myInputStream.read() >= 0) {
                        // may block the thread.
                        myInputStream.read();
                        bytes++;
                        // simulate a slow stream.
                        synchronized (this) {
                            wait(10);
                        }
                    }
                    everythingDone = true;
                } catch (IOException e) {
                    endedWithException = e;
                } catch (InterruptedException e) {
                    endedWithException = e;
                } finally {
                    timeOutThread.kill();
                    System.out.println("-->read " + bytes + " bytes.");
                    isRunning = false;
                    synchronized (this) {
                        notifyAll();
                    }
                }
            }
        }
    
        /**
         * Timeout Thread. Kill the main task if necessary.
         * 
         * @author el
         * 
         */
        public static class TimeOutThread extends Thread {
            final long timeout;
            final MainThread controlledObj;
    
            TimeOutThread(long timeout, MainThread controlledObj) {
                setDaemon(true);
                this.timeout = timeout;
                this.controlledObj = controlledObj;
            }
    
            boolean isRunning = true;
    
            /**
             * If we done need the {@link TimeOutThread} thread, we may kill it.
             */
            public void kill() {
                isRunning = false;
                synchronized (this) {
                    notify();
                }
            }
    
            /**
             * 
             */
            @Override
            public void run() {
                long deltaT = 0l;
                try {
                    long start = System.currentTimeMillis();
                    while (isRunning && deltaT < timeout) {
                        synchronized (this) {
                            wait(Math.max(100, timeout - deltaT));
                        }
                        deltaT = System.currentTimeMillis() - start;
                    }
                } catch (InterruptedException e) {
                    // If the thread is interrupted,
                    // you may not want to kill the main thread,
                    // but probably yes.
                } finally {
                    isRunning = false;
                }
                controlledObj.kill();
            }
        }
    
        /**
         * Start the main task and wait for the end.
         * 
         * @param args
         * @throws FileNotFoundException
         */
        public static void main(String[] args) throws FileNotFoundException {
            long start = System.currentTimeMillis();
            MainThread main = new MainThread(new File(args[0]));
            main.start();
            try {
                while (main.isRunning) {
                    synchronized (main) {
                        main.wait(1000);
                    }
                }
                long stop = System.currentTimeMillis();
    
                if (main.everythingDone)
                    System.out.println("all done in " + (stop - start) + " ms.");
                else {
                    System.out.println("could not do everything in "
                            + (stop - start) + " ms.");
                    if (main.endedWithException != null)
                        main.endedWithException.printStackTrace();
                }
            } catch (InterruptedException e) {
                System.out.println("You've killed me!");
            }
        }
    }
    

    Regards

    0 讨论(0)
  • 2020-11-22 01:35

    The following snippet will start an operation in a separate thread, then wait for up to 10 seconds for the operation to complete. If the operation does not complete in time, the code will attempt to cancel the operation, then continue on its merry way. Even if the operation cannot be cancelled easily, the parent thread will not wait for the child thread to terminate.

    ExecutorService executorService = getExecutorService();
    Future<SomeClass> future = executorService.submit(new Callable<SomeClass>() {
        public SomeClass call() {
            // Perform long-running task, return result. The code should check
            // interrupt status regularly, to facilitate cancellation.
        }
    });
    try {
        // Real life code should define the timeout as a constant or
        // retrieve it from configuration
        SomeClass result = future.get(10, TimeUnit.SECONDS);
        // Do something with the result
    } catch (TimeoutException e) {
        future.cancel(true);
        // Perform other error handling, e.g. logging, throwing an exception
    }
    

    The getExecutorService() method can be implemented in a number of ways. If you do not have any particular requirements, you can simply call Executors.newCachedThreadPool() for thread pooling with no upper limit on the number of threads.

    0 讨论(0)
  • 2020-11-22 01:37

    Assuming the thread code is out of your control:

    From the Java documentation mentioned above:

    What if a thread doesn't respond to Thread.interrupt?

    In some cases, you can use application specific tricks. For example, if a thread is waiting on a known socket, you can close the socket to cause the thread to return immediately. Unfortunately, there really isn't any technique that works in general. It should be noted that in all situations where a waiting thread doesn't respond to Thread.interrupt, it wouldn't respond to Thread.stop either. Such cases include deliberate denial-of-service attacks, and I/O operations for which thread.stop and thread.interrupt do not work properly.

    Bottom Line:

    Make sure all threads can be interrupted, or else you need specific knowledge of the thread - like having a flag to set. Maybe you can require that the task be given to you along with the code needed to stop it - define an interface with a stop() method. You can also warn when you failed to stop a task.

    0 讨论(0)
  • 2020-11-22 01:42

    There isn't a 100% reliable way to do this for any old task. The task has to be written with this ability in mind.

    Core Java libraries like ExecutorService cancel asynchronous tasks with interrupt() calls on the worker thread. So, for example, if the task contains some sort of loop, you should be checking its interrupt status on each iteration. If the task is doing I/O operations, they should be interruptible too—and setting that up can be tricky. In any case, keep in mind that code has to actively check for interrupts; setting an interrupt doesn't necessarily do anything.

    Of course, if your task is some simple loop, you can just check the current time at each iteration and give up when a specified timeout has elapsed. A worker thread isn't needed in that case.

    0 讨论(0)
  • 2020-11-22 01:43

    I created a helper class just for this some time ago. Works great:

    import java.util.concurrent.BrokenBarrierException;
    import java.util.concurrent.CyclicBarrier;
    /**
     * TimeOut class - used for stopping a thread that is taking too long
     * @author Peter Goransson
     *
     */
    public class TimeOut {
    
        Thread interrupter;
        Thread target;
        long timeout;
        boolean success;
        boolean forceStop;
    
        CyclicBarrier barrier;
    
        /**
         * 
         * @param target The Runnable target to be executed
         * @param timeout The time in milliseconds before target will be interrupted or stopped
         * @param forceStop If true, will Thread.stop() this target instead of just interrupt() 
         */
        public TimeOut(Runnable target, long timeout, boolean forceStop) {      
            this.timeout = timeout;
            this.forceStop = forceStop;
    
            this.target = new Thread(target);       
            this.interrupter = new Thread(new Interrupter());
    
            barrier = new CyclicBarrier(2); // There will always be just 2 threads waiting on this barrier
        }
    
        public boolean execute() throws InterruptedException {  
    
            // Start target and interrupter
            target.start();
            interrupter.start();
    
            // Wait for target to finish or be interrupted by interrupter
            target.join();  
    
            interrupter.interrupt(); // stop the interrupter    
            try {
                barrier.await(); // Need to wait on this barrier to make sure status is set
            } catch (BrokenBarrierException e) {
                // Something horrible happened, assume we failed
                success = false;
            } 
    
            return success; // status is set in the Interrupter inner class
        }
    
        private class Interrupter implements Runnable {
    
            Interrupter() {}
    
            public void run() {
                try {
                    Thread.sleep(timeout); // Wait for timeout period and then kill this target
                    if (forceStop) {
                      target.stop(); // Need to use stop instead of interrupt since we're trying to kill this thread
                    }
                    else {
                        target.interrupt(); // Gracefully interrupt the waiting thread
                    }
                    System.out.println("done");             
                    success = false;
                } catch (InterruptedException e) {
                    success = true;
                }
    
    
                try {
                    barrier.await(); // Need to wait on this barrier
                } catch (InterruptedException e) {
                    // If the Child and Interrupter finish at the exact same millisecond we'll get here
                    // In this weird case assume it failed
                    success = false;                
                } 
                catch (BrokenBarrierException e) {
                    // Something horrible happened, assume we failed
                    success = false;
                }
    
            }
    
        }
    }
    

    It is called like this:

    long timeout = 10000; // number of milliseconds before timeout
    TimeOut t = new TimeOut(new PhotoProcessor(filePath, params), timeout, true);
    try {                       
      boolean sucess = t.execute(); // Will return false if this times out
      if (!sucess) {
        // This thread timed out
      }
      else {
        // This thread ran completely and did not timeout
      }
    } catch (InterruptedException e) {}  
    
    0 讨论(0)
  • 2020-11-22 01:46

    In the solution given by BalusC, the main thread will stay blocked for the timeout period. If you have a thread pool with more than one thread, you will need the same number of additional thread that will be using Future.get(long timeout,TimeUnit unit) blocking call to wait and close the thread if it exceeds the timeout period.

    A generic solution to this problem is to create a ThreadPoolExecutor Decorator that can add the timeout functionality. This Decorator class should create as many threads as ThreadPoolExecutor has, and all these threads should be used only to wait and close the ThreadPoolExecutor.

    The generic class should be implemented like below:

    import java.util.List;
    import java.util.concurrent.*;
    
    public class TimeoutThreadPoolDecorator extends ThreadPoolExecutor {
    
    
        private final ThreadPoolExecutor commandThreadpool;
        private final long timeout;
        private final TimeUnit unit;
    
        public TimeoutThreadPoolDecorator(ThreadPoolExecutor threadpool,
                                          long timeout,
                                          TimeUnit unit ){
            super(  threadpool.getCorePoolSize(),
                    threadpool.getMaximumPoolSize(),
                    threadpool.getKeepAliveTime(TimeUnit.MILLISECONDS),
                    TimeUnit.MILLISECONDS,
                    threadpool.getQueue());
    
            this.commandThreadpool = threadpool;
            this.timeout=timeout;
            this.unit=unit;
        }
    
        @Override
        public void execute(Runnable command) {
            super.execute(() -> {
                Future<?> future = commandThreadpool.submit(command);
                try {
                    future.get(timeout, unit);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                } catch (ExecutionException | TimeoutException e) {
                    throw new RejectedExecutionException(e);
                } finally {
                    future.cancel(true);
                }
            });
        }
    
        @Override
        public void setCorePoolSize(int corePoolSize) {
            super.setCorePoolSize(corePoolSize);
            commandThreadpool.setCorePoolSize(corePoolSize);
        }
    
        @Override
        public void setThreadFactory(ThreadFactory threadFactory) {
            super.setThreadFactory(threadFactory);
            commandThreadpool.setThreadFactory(threadFactory);
        }
    
        @Override
        public void setMaximumPoolSize(int maximumPoolSize) {
            super.setMaximumPoolSize(maximumPoolSize);
            commandThreadpool.setMaximumPoolSize(maximumPoolSize);
        }
    
        @Override
        public void setKeepAliveTime(long time, TimeUnit unit) {
            super.setKeepAliveTime(time, unit);
            commandThreadpool.setKeepAliveTime(time, unit);
        }
    
        @Override
        public void setRejectedExecutionHandler(RejectedExecutionHandler handler) {
            super.setRejectedExecutionHandler(handler);
            commandThreadpool.setRejectedExecutionHandler(handler);
        }
    
        @Override
        public List<Runnable> shutdownNow() {
            List<Runnable> taskList = super.shutdownNow();
            taskList.addAll(commandThreadpool.shutdownNow());
            return taskList;
        }
    
        @Override
        public void shutdown() {
            super.shutdown();
            commandThreadpool.shutdown();
        }
    }
    

    The above decorator can be used as below:

    import java.util.concurrent.SynchronousQueue;
    import java.util.concurrent.ThreadPoolExecutor;
    import java.util.concurrent.TimeUnit;
    
    public class Main {
    
        public static void main(String[] args){
    
            long timeout = 2000;
    
            ThreadPoolExecutor threadPool = new ThreadPoolExecutor(3, 10, 0, TimeUnit.MILLISECONDS, new SynchronousQueue<>(true));
    
            threadPool = new TimeoutThreadPoolDecorator( threadPool ,
                    timeout,
                    TimeUnit.MILLISECONDS);
    
    
            threadPool.execute(command(1000));
            threadPool.execute(command(1500));
            threadPool.execute(command(2100));
            threadPool.execute(command(2001));
    
            while(threadPool.getActiveCount()>0);
            threadPool.shutdown();
    
    
        }
    
        private static Runnable command(int i) {
    
            return () -> {
                System.out.println("Running Thread:"+Thread.currentThread().getName());
                System.out.println("Starting command with sleep:"+i);
                try {
                    Thread.sleep(i);
                } catch (InterruptedException e) {
                    System.out.println("Thread "+Thread.currentThread().getName()+" with sleep of "+i+" is Interrupted!!!");
                    return;
                }
                System.out.println("Completing Thread "+Thread.currentThread().getName()+" after sleep of "+i);
            };
    
        }
    }
    
    0 讨论(0)
提交回复
热议问题