Как остановить поток - PullRequest
       35

Как остановить поток

236 голосов
/ 16 февраля 2010

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

Один из способов сделать это, как я понял из этой темы это использовать TimerTask внутри метода run () потока.

Есть ли лучшие решения для этого?


РЕДАКТИРОВАТЬ: Добавление щедрости, как мне нужно более четкий ответ. Приведенный ниже код ExecutorService не решает мою проблему. Почему я должен спать () после выполнения (некоторый код - у меня нет никакого контроля над этим куском кода)? Если код завершен, а sleep () прерван, как это может быть timeOut?

Задача, которую нужно выполнить, находится вне моего контроля. Это может быть любой кусок кода. Проблема в том, что этот кусок кода может попасть в бесконечный цикл. Я не хочу, чтобы это случилось. Итак, я просто хочу запустить эту задачу в отдельном потоке. Родительский поток должен ждать, пока этот поток не завершится, и должен знать состояние задачи (т. Е. Истекло ли время или произошло какое-то исключение, или если оно успешно). Если задача входит в бесконечный цикл, мой родительский поток продолжает ждать бесконечно, что не является идеальной ситуацией.

Ответы [ 17 ]

350 голосов
/ 16 февраля 2010

На самом деле лучше использовать ExecutorService вместо Timer, вот SSCCE :

package com.stackoverflow.q2275443;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class Test {
    public static void main(String[] args) throws Exception {
        ExecutorService executor = Executors.newSingleThreadExecutor();
        Future<String> future = executor.submit(new Task());

        try {
            System.out.println("Started..");
            System.out.println(future.get(3, TimeUnit.SECONDS));
            System.out.println("Finished!");
        } catch (TimeoutException e) {
            future.cancel(true);
            System.out.println("Terminated!");
        }

        executor.shutdownNow();
    }
}

class Task implements Callable<String> {
    @Override
    public String call() throws Exception {
        Thread.sleep(4000); // Just to demo a long running task of 4 seconds.
        return "Ready!";
    }
}

Воспроизвести немного с аргументом timeout в методе Future#get(), например, увеличьте его до 5, и вы увидите, что поток завершается. Вы можете перехватить таймаут в блоке catch (TimeoutException e).

Обновление: Чтобы прояснить концептуальное недоразумение, sleep() - это , а не . Это просто используется для SSCCE / демонстрационных целей. Просто сделайте ваше долгосрочное задание прямо вместо sleep(). Внутри вашей длительной задачи вы должны проверить, не прерывается ли поток следующим образом:

while (!Thread.interrupted()) {
    // Do your long running task here.
}
44 голосов
/ 16 февраля 2010

Нет 100% надежного способа сделать это для любой старой задачи. Задача должна быть написана с учетом этой способности.

Базовые библиотеки Java, такие как ExecutorService, отменяют асинхронные задачи с помощью вызовов interrupt() в рабочем потоке. Так, например, если задача содержит какой-то цикл, вы должны проверять ее состояние прерывания на каждой итерации. Если задача выполняет операции ввода-вывода, они также должны быть прерываемыми - и их настройка может быть сложной. В любом случае имейте в виду, что код должен активно проверять наличие прерываний; установка прерывания не обязательно делает что-либо.

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

13 голосов
/ 16 февраля 2010

Рассмотрите возможность использования экземпляра ExecutorService . Оба метода invokeAll() и invokeAny() доступны с параметром timeout.

Текущий поток будет блокироваться до тех пор, пока метод не завершится (не уверен, если это желательно) либо потому, что задача (и) завершена нормально, либо истекло время ожидания. Вы можете проверить возвращенные Future (s), чтобы определить, что произошло.

8 голосов
/ 03 апреля 2012

BalusC сказал:

Обновление: чтобы прояснить концептуальное недоразумение, сон () не требуется. Это просто используется для SSCCE / демонстрационных целей. Просто сделайте свое долгосрочное задание прямо вместо сна ().

Но если вы замените Thread.sleep(4000); на for (int i = 0; i < 5E8; i++) {}, он не скомпилируется, потому что пустой цикл не выдает InterruptedException.

И для прерывания потока необходимо бросить InterruptedException.

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

Отредактировано, чтобы добавить: я снова задала этот вопрос как новый вопрос: [ прерывание потока через фиксированное время, должно ли оно выдавать InterruptedException? ]

7 голосов
/ 16 июля 2011

Предполагается, что код потока находится вне вашего контроля:

Из документации Java , упомянутой выше:

Что если поток не отвечает на Thread.interrupt?

В некоторых случаях вы можете использовать специальные приемы приложения. Например, если поток ожидает на известном сокете, вы можете закрыть сокет заставить поток вернуться немедленно. К сожалению, там действительно не любая техника, которая работает в целом. Следует отметить, что в все ситуации, когда ожидающий поток не отвечает на Thread.interrupt, он также не будет отвечать на Thread.stop. Такой случаи включают преднамеренные атаки типа «отказ в обслуживании» и операции ввода-вывода для которых thread.stop и thread.interrupt не работают должным образом.

Итог:

Убедитесь, что все потоки могут быть прерваны, или же вам нужны специальные знания о потоке - например, наличие флага для установки. Возможно, вы можете потребовать, чтобы вам дали задачу вместе с кодом, необходимым для ее остановки - определите интерфейс с помощью метода stop(). Вы также можете предупредить, что не удалось остановить задачу.

5 голосов
/ 30 августа 2012

Я создал вспомогательный класс только для этого некоторое время назад. Прекрасно работает:

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

        }

    }
}

Это называется так:

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) {}  
5 голосов
/ 24 февраля 2010

Я думаю, вы должны взглянуть на правильные механизмы обработки параллелизма (потоки, идущие в бесконечные циклы, звучат не очень хорошо сами по себе, кстати). Обязательно прочитайте немного о теме "убийство" или "остановка" .

То, что вы описываете, очень похоже на «рандеву», поэтому вы можете взглянуть на CyclicBarrier .

Могут быть и другие конструкции (например, с использованием CountDownLatch ), которые могут решить вашу проблему (один поток ожидает время ожидания для защелки, другой должен отсчитывать время защелки, если он выполнил свою работу , который освободит ваш первый поток либо после тайм-аута, либо при активном обратном отсчете защелки).

Обычно я рекомендую две книги в этой области: Параллельное программирование на Java и Параллелизм Java на практике .

3 голосов
/ 25 февраля 2010

Я публикую вам фрагмент кода, который показывает способ решения проблемы. В качестве примера я читаю файл. Вы можете использовать этот метод для другой операции, но вам нужно реализовать метод kill (), чтобы основная операция была прервана.

надеюсь, это поможет


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

Привет

2 голосов
/ 10 июня 2015

Отличный ответ от BalusC:

но только для того, чтобы добавить, что само время ожидания не прерывает сам поток. даже если вы проверяете с помощью while (! Thread.interrupted ()) в своей задаче. если вы хотите убедиться, что поток остановлен, вы также должны убедиться, что future.cancel () вызывается, когда исключение тайм-аута перехватывается.

package com.stackoverflow.q2275443; 

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;


public class Test { 
    public static void main(String[] args) throws Exception {
        ExecutorService executor = Executors.newSingleThreadExecutor();
        Future<String> future = executor.submit(new Task());

        try { 
            System.out.println("Started..");
            System.out.println(future.get(3, TimeUnit.SECONDS));
            System.out.println("Finished!");
        } catch (TimeoutException e) {
            //Without the below cancel the thread will continue to live 
            // even though the timeout exception thrown.
            future.cancel();
            System.out.println("Terminated!");
        } 

        executor.shutdownNow();
    } 
} 

class Task implements Callable<String> {
    @Override 
    public String call() throws Exception {
      while(!Thread.currentThread.isInterrupted()){
          System.out.println("Im still running baby!!");
      }          
    } 
} 
2 голосов
/ 14 декабря 2013

Вот мой действительно простой в использовании вспомогательный класс для запуска или вызов фрагмент кода Java: -)

Это основано на превосходном ответе от BalusC

package com.mycompany.util.concurrent;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
 * Calling {@link Callable#call()} or Running {@link Runnable#run()} code
 * with a timeout based on {@link Future#get(long, TimeUnit))}
 * @author pascaldalfarra
 *
 */
public class CallableHelper
{

    private CallableHelper()
    {
    }

    public static final void run(final Runnable runnable, int timeoutInSeconds)
    {
        run(runnable, null, timeoutInSeconds);
    }

    public static final void run(final Runnable runnable, Runnable timeoutCallback, int timeoutInSeconds)
    {
        call(new Callable<Void>()
        {
            @Override
            public Void call() throws Exception
            {
                runnable.run();
                return null;
            }
        }, timeoutCallback, timeoutInSeconds); 
    }

    public static final <T> T call(final Callable<T> callable, int timeoutInSeconds)
    {
        return call(callable, null, timeoutInSeconds); 
    }

    public static final <T> T call(final Callable<T> callable, Runnable timeoutCallback, int timeoutInSeconds)
    {
        ExecutorService executor = Executors.newSingleThreadExecutor();
        try
        {
            Future<T> future = executor.submit(callable);
            T result = future.get(timeoutInSeconds, TimeUnit.SECONDS);
            System.out.println("CallableHelper - Finished!");
            return result;
        }
        catch (TimeoutException e)
        {
            System.out.println("CallableHelper - TimeoutException!");
            if(timeoutCallback != null)
            {
                timeoutCallback.run();
            }
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        catch (ExecutionException e)
        {
            e.printStackTrace();
        }
        finally
        {
            executor.shutdownNow();
            executor = null;
        }

        return null;
    }

}
...