Как немедленно остановить задачу, запущенную с помощью ExecutorService?

Я пробовал много разных способов немедленно остановить задачу, запущенную с помощью ExecutorService, но безуспешно.

Future<Void> future = executorService.submit(new Callable<Void>(
    public Void call () {
        ... do many other things here..
        if(Thread.currentThread.isInterrupted()) {
            return null;
        }
        ... do many other things here..
        if(Thread.currentThread.isInterrupted()) {
            return null;
        }
    }
));

if(flag) { // may be true and directly cancel the task
    future.cancel(true);
}

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

Проблема в том, что после вызова future.cancel(true) задача не останавливается и Thread.currentThread.isInterrupted() по-прежнему возвращает false. и я не могу узнать, что задача была остановлена ​​внутри метода call().

Я думаю установить флаг типа cancelled=true после вызова future.cancel(true) и постоянной проверки этого флага в методе call(), я думаю, что это хак, и код может быть очень некрасиво, потому что пользователь может запускать много задач одновременно.

Есть ли более элегантный способ добиться того, чего я хочу?

ИЗМЕНИТЬ:

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

Я делаю следующее, чтобы запустить 5 задач, каждая задача запускает 5 потоков для загрузки файла. а затем немедленно останавливаю все 5 задач. Для всех приведенных ниже вызовов методов я запускаю поток (ExecutorService.submit(task)), чтобы сделать его асинхронным, как вы можете судить по суффиксам методов.

int t1 = startTaskAysnc(task1);
int t2 = startTaskAysnc(task2);
int t3 = startTaskAysnc(task3);
int t4 = startTaskAysnc(task4);
int t5 = startTaskAysnc(task5);

int stopTaskAysnc(t1);
int stopTaskAysnc(t2);
int stopTaskAysnc(t3);
int stopTaskAysnc(t4);
int stopTaskAysnc(t5);

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

public void startTaskAsync(DownloadTask task) { 
    Future<Void> future = executorService.submit(new Callable<Void>(
        public Void call () {
            // this is a synchronous call
            int fileSize = getFileSize();
            System.out.println(Thread.currentThread.isInterrupted());
            ....
            Future<Void> futures = new Future<Void>[5];
            for (int i = 0; i < futures.length; ++i) {
                futures[i] = executorService.submit(new Callable<Void>(){...});
            }

            for (int i = 0; i < futures.length; ++i) {
                futures[i].get(); // wait for it to complete
            }            
        }
    ));
    synchronized (mTaskMap) {
        mTaskMap.put(task.getId(), future);
    }
}

public void stopTaskAysnc(int taskId) {
    executorService.execute(new Runnable(){
        Future<Void> future = mTaskMap.get(taskId);
        future.cancel(true);
    });
}

Я заметил странное поведение: после того, как я вызвал stopTaskAsync() для всех 5 задач, всегда будет по крайней мере одна задача, которая будет остановлена (т.е. Thread.currentThread.isInterrupted() вернет true), и остальные 4 задачи продолжали работать.

И я попробовал ваши предложения, установив UncaughtExceptionHandler, но из этого ничего не выходит.

ИЗМЕНИТЬ:

Проблема была решена по этой ссылке: Не удается остановить задачу который запускается с помощью ExecutorService


person neevek    schedule 26.12.2011    source источник


Ответы (3)


Что ж, javadoc из Future.cancel(boolean) говорит, что:

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

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

... делать здесь много других вещей..

случайно очищает статус interrupted Thread без выполнения желаемой обработки. Если вы поставите точку останова в Thread.interrupt(), вы можете поймать преступника.

Другой вариант, который я могу придумать, заключается в том, что задача завершается до захвата прерывания, либо потому, что она завершена, либо выдает какое-то неперехваченное исключение. Позвоните Future.get(), чтобы определить это. В любом случае, как уже упоминал asdasd, хорошей практикой является установка UncaughtExceptionHandler.

person yair    schedule 26.12.2011
comment
Привет, @yair, я отредактировал свой вопрос, пожалуйста, помогите мне, если у вас есть подсказка. Спасибо вам. - person neevek; 26.12.2011

То, что вы делаете, очень опасно: вы используете пул потоков для выполнения задач (которые я буду называть загрузчиками) и тот же пул потоков для выполнения задач, которые

  • подождите, пока закончатся загрузчики (которые я буду называть контроллерами)
  • или попросить диспетчеров остановиться

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

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

person JB Nizet    schedule 26.12.2011
comment
Привет, Дж. Б. Низе. Вы правы, я знал об этом, прежде чем публиковать вопрос здесь, но проблема, с которой я столкнулся, похоже, не имеет ничего общего со стратегией использования пула потоков, потому что я обеспечил минимальное количество потоков, достаточное для запуска всех контроллеры и загрузчики на 5 задач. В любом случае я приму ваше предложение и использую 2 отдельных пула потоков для запуска контроллеров и загрузчиков и отмены контроллеров в текущем потоке. - person neevek; 26.12.2011

Я думаю, вы найдете решение здесь. Суть в том, что метод отмены вызывает InterruptedException. Пожалуйста, проверьте, работает ли ваша тема после отмены? Вы уверены, что не пытались прервать завершенный поток? Вы уверены, что ваш поток не завершился сбоем с каким-либо другим исключением? Попробуйте настроить UncaughtExceptionHandler.

person Ilya    schedule 26.12.2011
comment
Привет, @asdasd, я отредактировал свой вопрос, пожалуйста, помогите мне, если у вас есть подсказка. Спасибо вам. - person neevek; 26.12.2011