无法中断 ExecutorService 的任务

Can't interrupt tasks of ExecutorService

编辑:

为了在 Android 环境之外测试这个问题,我创建了一个 Java 应用程序,它创建了一个 ExecutorService,提供了一个 AttackScript 的任务(相同 class) 然后终止。

按预期工作 100%,线程被中断,任务停止。

您甚至不必通过 Future.cancel(true) 取消任务。 ExecutorService.shutdownNow() 完成任务。 Android 的 Service 中有什么东西以某种方式扰乱了线程池吗?

按预期工作的代码:

public static void main(String[] args) {
        AttackScript script = new AttackScript("http://ninjaflex.com/");

        ExecutorService executor = Executors.newFixedThreadPool(5);
        executor.submit(script);
        executor.submit(script);
        executor.submit(script);
        executor.submit(script);

        sleep(1300);

        // Automatically interrupts threads in the pool.
        executor.shutdownNow();
    }

    private static void sleep(long timeMilli){
        try {
            Thread.sleep(timeMilli);
        } catch(Exception e) {
            System.out.println("Error sleep()");
        }
    }

原post:

我有一个 Android Service,其中包含一个 ExecutorService 字段,负责 运行 一些任务。

任务是 AttackScript class 的对象。我将 Future 引用缓存在一个名为任务的 Map<String,Future> 中,这样我以后就可以取消它们。

Future future = executor.submit(new AttackScript(attack.getWebsite()));
tasks.put(attack.getPushId(), future);

ServiceonDestroy()(当用户按下通知按钮时调用)我正在取消所有任务

private void cancelAllTasks() {
    for (Map.Entry<String, Future> futureEntry : tasks.entrySet()) {
        futureEntry.getValue().cancel(true);
    }
}

然后关闭执行器:

private void shutdownThreadPool() {
     // https://www.baeldung.com/java-executor-service-tutorial
     executor.shutdown();
     try {
         if (executor.awaitTermination(800, TimeUnit.MILLISECONDS))
                executor.shutdownNow();
     } catch (InterruptedException e) {
            executor.shutdownNow();
     }
}

最后是 AttackScript class:

public class AttackScript implements Runnable {
    private static final String TAG = "AttackScript";
    private URL url;

    public AttackScript(String website) {
        initializeUrl(website);
    }

    private void initializeUrl(String website) {
        try {
            url = new URL(website);
        } catch (MalformedURLException e) {
            Log.e(TAG, "Wrong url?", e);
        }
    }

    @Override
    public void run() {
        while (!Thread.currentThread().isInterrupted()) {
            readUrl();
        }
        Log.d(TAG, "Stopped requesting from " + url + " server.");
    }

    private void readUrl() {
        InputStream in = null;
        try {
            in = url.openStream();
        } catch (IOException e) {
            Log.e(TAG, "openStream() error.", e);
        } finally {
            closeInputStream(in);
        }
    }

    private void closeInputStream(InputStream in) {
        try {
            in.close();
            Log.d(TAG, "InputStream closed for " + url);
        } catch (IOException e) {
            Log.e(TAG, "Error while closing the input stream.", e);
        }
    }
}

奇怪的是,很少有十分之一的任务被中断并且 AttackScript 的执行停止。但是其他 9 个任务没有被打断,在 URLs.

上继续 openStreams()

被迫寻找替代解决方案我完全删除 线程池的使用,现在实施 Threads,存储在Map.

再次中断从未发生,因此 AtomicBoolean 现在正在控制线程的执行。

private AtomicBoolean stopped = new AtomicBoolean(false);

 @Override
    public void run() {
        while (!stopped.get()) {
            readUrl();
        }
}

public void stopExecution() {
        stopped.set(true);
}

这是一个孤注一掷的举动,但迄今为止唯一有效的举动。

您已经回答了一个有效的解决方法来避免这个问题,但我会解释原因。故障不在于 ExecutorService,而在于线程的中断状态被网络库静默清除。

正如您和另一位评论者所发现的,这很可能取决于您使用的特定设备及其 Android 版本。

从 Android 4.4 开始,OkHttp 用作 HttpUrlConnection。每个线程何时中断与 InputStream 在旧版本中是否已关闭之间存在竞争条件。

作为 close() 调用的一部分,这段代码最终被执行:

public void throwIfReached() throws IOException {
    if (Thread.interrupted()) {
        throw new InterruptedIOException("thread interrupted");
    }

    if (hasDeadline && deadlineNanoTime - System.nanoTime() <= 0) {
        throw new InterruptedIOException("deadline reached");
    }
}

你可以看到基于Thread.interrupted()调用它清除了线程的中断状态并且再也没有设置它。

更糟糕的是,您似乎可以改为依赖 InterruptedIOException,但在关闭流时会在内部静默处理,因此您没有机会处理它。

当我使用更新版本的 OkHttp 时,您的代码示例对我有用。在以后的版本中,看起来更注意保持中断状态并且它实际上按预期工作。

但是,根据一些搜索,从历史上看,中断似乎不能很好地与 OkHttp 配合使用,为了停止请求,他们建议尽可能使用 Call.cancel()