Как я могу обернуть метод, чтобы я мог убить его выполнение, если он превышает указанное время ожидания? - PullRequest
15 голосов
/ 27 октября 2008

У меня есть метод, который я хотел бы вызвать. Однако я ищу простой и простой способ убить его или заставить его вернуться, если выполнение занимает слишком много времени.

Я использую Java.

для иллюстрации:

logger.info("sequentially executing all batches...");
for (TestExecutor executor : builder.getExecutors()) {
logger.info("executing batch...");
executor.execute();
}

Я считаю, что класс TestExecutor должен implement Callable и продолжать в этом направлении.

Но все, что я хочу, - это остановиться executor.execute(), если это займет слишком много времени.

Предложения ...

EDIT

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

Ответы [ 7 ]

11 голосов
/ 28 октября 2008

Вы должны взглянуть на эти классы: FutureTask , Callable , Исполнители

Вот пример:

public class TimeoutExample {
    public static Object myMethod() {
        // does your thing and taking a long time to execute
        return someResult;
    }

    public static void main(final String[] args) {
        Callable<Object> callable = new Callable<Object>() {
            public Object call() throws Exception {
                return myMethod();
            }
        };
        ExecutorService executorService = Executors.newCachedThreadPool();

        Future<Object> task = executorService.submit(callable);
        try {
            // ok, wait for 30 seconds max
            Object result = task.get(30, TimeUnit.SECONDS);
            System.out.println("Finished with result: " + result);
        } catch (ExecutionException e) {
            throw new RuntimeException(e);
        } catch (TimeoutException e) {
            System.out.println("timeout...");
        } catch (InterruptedException e) {
            System.out.println("interrupted");
        }
    }
}
8 голосов
/ 27 октября 2008

Java механизм прерывания предназначен для такого рода сценариев. Если метод, который вы хотите прервать, выполняет цикл, просто попросите его проверить прерванное состояние потока на каждой итерации. Если оно прервано, создайте исключение InterruptedException.

Затем, когда вы хотите прервать, вам просто нужно вызвать прерывание в соответствующем потоке.

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

7 голосов
/ 27 октября 2008

Я предполагаю использование нескольких потоков в следующих инструкциях.

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

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

3 голосов
/ 27 октября 2008

На самом деле, вы не можете ... Единственный способ сделать это - использовать thread.stop, договориться о «кооперативном» методе (например, иногда проверять Thread.isInterrupted или вызывать метод, который выбрасывает InterruptedException, например Thread.sleep ()), или каким-либо образом вызвать метод в другой JVM полностью.

Для некоторых видов тестов вызов stop () - это нормально, но он, вероятно, повредит состоянию вашего набора тестов, поэтому вам придется перезапускать JVM после каждого вызова stop (), если вы хотите избежать взаимодействия эффекты.

Чтобы получить хорошее описание того, как реализовать совместный подход, ознакомьтесь с Часто задаваемыми вопросами Sun об устаревших методах Thread .

В качестве примера такого подхода в реальной жизни Объект задания API-интерфейса Eclipse RCP IProgressMonitor позволяет некоторым службам управления сигнализировать подпроцессам (с помощью метода «отмена»), что они должны остановиться. Конечно, это зависит от методов, которые регулярно проверяют метод isCancelled, чего они часто не в состоянии сделать.

Гибридный подход может заключаться в том, чтобы красиво задать потоку прерывание, а затем через пару секунд настаивать на остановке. Опять же, вы не должны использовать stop в рабочем коде, но в этом случае все может быть хорошо, особенно. если вы вскоре выйдете из JVM.

Чтобы проверить этот подход, я написал простой жгут, который берет работоспособный объект и пытается его выполнить. Не стесняйтесь комментировать / редактировать.

public void testStop(Runnable r) {
    Thread t = new Thread(r);
    t.start();
    try {
        t.join(2000);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }

    if (!t.isAlive()) {
        System.err.println("Finished on time.");
        return;
    }

    try {
        t.interrupt();
        t.join(2000);
        if (!t.isAlive()) {
            System.err.println("cooperative stop");
            return;
        }
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
    System.err.println("non-cooperative stop");
    StackTraceElement[] trace = Thread.getAllStackTraces().get(t);
    if (null != trace) {
        Throwable temp = new Throwable();
        temp.setStackTrace(trace);
        temp.printStackTrace();
    }
    t.stop();
    System.err.println("stopped non-cooperative thread");
}

Чтобы проверить это, я написал два конкурирующих бесконечных цикла, один кооператив и тот, который никогда не проверяет прерванный бит своего потока.

public void cooperative() {
    try {
        for (;;) {
            Thread.sleep(500);
        }
    } catch (InterruptedException e) {
        System.err.println("cooperative() interrupted");
    } finally {
        System.err.println("cooperative() finally");
    }
}

public void noncooperative() {
    try {
        for (;;) {
            Thread.yield();
        }
    } finally {
        System.err.println("noncooperative() finally");
    }
}

Наконец, я написал тесты (JUnit 4) для их проверки:

@Test
public void testStopCooperative() {
    testStop(new Runnable() {
        @Override
        public void run() {
            cooperative();
        }
    });
}

@Test
public void testStopNoncooperative() {
    testStop(new Runnable() {
        @Override
        public void run() {
            noncooperative();
        }
    });
}

Раньше я никогда не использовал Thread.stop (), поэтому я не знал о его работе. Он работает, выбрасывая объект ThreadDeath от того места, где в данный момент работает целевой поток. Это расширяет ошибку. Таким образом, хотя он не всегда работает чисто, он обычно оставляет простые программы с довольно разумным состоянием программы. Например, любые блоки finally называются. Если вы хотите быть настоящим придурком, вы можете поймать ThreadDeath (или Ошибка) и продолжать работать, в любом случае!

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

Что касается метода «начать другую JVM полностью», он потребует больше работы. Я не знаю, написал ли кто-нибудь загрузчик делегирующего класса или он включен в JVM, но это потребуется для этого подхода.

1 голос
/ 27 октября 2008

Никто не ответил на это напрямую, так что вот что я могу вам дать в коротком количестве кода псевдо:

обернуть метод в работоспособный / вызываемый. Самому методу придется проверять наличие прерванного состояния, если вы хотите, чтобы он остановился (например, если этот метод является циклом, внутри проверки цикла для Thread.currentThread (). IsInterrupted и, если это так, остановить цикл (don не проверяйте каждую итерацию, или вы просто замедлитесь. в методе упаковки используйте thread.join (timeout), чтобы дождаться времени, когда вы захотите запустить метод. или, внутри цикла, вызовите join несколько раз с меньшим временем ожидания, если вам нужно сделать другие вещи во время ожидания. если метод не завершается, после присоединения используйте приведенные выше рекомендации для отмены быстрой / чистой.

так что код мудрый, старый код:

void myMethod()
{
    methodTakingAllTheTime();
}

новый код:

void myMethod()
{
    Thread t = new Thread(new Runnable()
        {
            public void run()
            {
                 methodTakingAllTheTime(); // modify the internals of this method to check for interruption
            }
        });
    t.join(5000); // 5 seconds
    t.interrupt();
}

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

0 голосов
/ 06 мая 2014

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

 Method(){
 //task1
if (tooMuchTime == true) return;
 //task2
if (tooMuchTime == true) return;
 //task3
if (tooMuchTime == true) return;
//task4
if (tooMuchTime == true) return;
//task5
if (tooMuchTime == true) return;
//final task
  }
0 голосов
/ 19 апреля 2013

Я считаю, что правильный ответ - создать Runnable для выполнения подпрограммы и запустить его в отдельном потоке. Runnable может быть FutureTask, который вы можете запустить с тайм-аутом (метод «get»). Если время истекло, вы получите исключение TimeoutException, в котором я предлагаю вам

  • вызовите thread.interrupt (), чтобы попытаться завершить его полукооперативным способом (многие библиотечные вызовы чувствительны к этому, поэтому, вероятно, это будет работать)
  • подожди немного (Thread.sleep (300))
  • и затем, если поток все еще активен (thread.isActive ()), вызовите thread.stop (). Это устаревший метод, но, видимо, единственная игра в городе, в которой не хватает отдельного процесса со всеми вытекающими последствиями.

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

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

По моим исследованиям, в Java, похоже, нет недревесного положения для запуска некооперативного кода, что, в некотором смысле, является пробелом в модели безопасности. Либо я могу запустить сторонний код и управлять имеющимися у него разрешениями (SecurityManager), либо я не могу запустить чужой код, потому что он может в конечном итоге занять весь ЦП без каких-либо устаревших средств, чтобы остановить его.

double x = 2.0;  
while(true) {x = x*x}; // do not terminate
System.out.print(x); // prevent optimization
...