使用 ThreadPoolExecutor 取消 SwingWorker

发布于 2024-07-25 15:48:15 字数 656 浏览 6 评论 0原文

我正在使用线程池大小为 1 的 ThreadPoolExecutor 来顺序执行 swing 工作线程。 我遇到了一个特殊情况,其中一个事件到达,创建了一个 Swing Worker,它执行一些客户端-服务器通信,然后更新 ui(在 did() 方法中)。

当用户触发(单击某个项目)某些事件时,此方法可以正常工作,但如果发生很多事件,则效果不佳。 但发生这种情况,所以我需要取消所有当前正在运行和计划的工作人员。 问题是支持 ThreadPoolExecutor 的队列不知道 SwingWorker 取消过程(至少看起来是这样)。 因此,预定的工作人员会被取消,但已经运行的工作人员不会被取消。

因此,我添加了一个 类型的并发队列,它保存所有工作人员的引用,只要它们没有被取消,并且当新事件到达时,它会对所有工作人员调用 .cancel(true)将 SwingWorker 放入队列中并将新的 SwingWorker 提交给 ThreadPoolExecutor。

摘要: SwingWorkers 在具有单个线程的 ThreadPoolExecutor 中创建和执行。 只有最后提交的工作人员才应该运行。

有没有其他方法可以解决这个问题,或者这样做“可以”吗?

只是好奇...

i am using a ThreadPoolExecutor with a thread pool size of one to sequentially execute swing workers. I got a special case where an event arrives that creates a swing worker that does some client-server communication and after that updates the ui (in the done() method).

This works fine when the user fires (clicks on an item) some events but not if there occur many of them. But this happens so i need to cancel all currently running and scheduled workers. The problem is that the queue that is backing the ThreadPoolExecutor isnt aware of the SwingWorker cancellation process (at least it seems like that). So scheduled worker get cancelled but already running workers get not.

So i added a concurrent queue of type <T extends SwingWorker> that holds a reference of all workers as long as they are not cancelled and when a new event arrives it calls .cancel(true) on all SwingWorkers in the queue and submits the new SwingWorker to the ThreadPoolExecutor.

Summary: SwingWorkers are created and executed in a ThreadPoolExecutor with a single Thread. Only the worker that was submitted last should be running.

Are there any alternatives to solve this problem, or is it "ok" to do it like this?

Just curious...

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

夏至、离别 2024-08-01 15:48:16

为什么需要 ThreadPoolExecutor 来完成这种工作?

您有多少个不同 SwingWorkers 的来源? 因为如果来源只是一个,您应该使用不同的方法。

例如,您可以定义一个处理一种工作线程的类,并将其链接到一种项目,用户可以在该项目上触发操作并在该类中关心应该运行线程的单个实例的事实(例如使用完成任务后清除的单例实例)

Why do you need a ThreadPoolExecutor to do this kind of job?

How many sources of different SwingWorkers you have? Because if the source is just one you should use a different approach.

For example you can define a class that handles one kind of working thread and it's linked to a single kind of item on which the user can fire actions and care inside that class the fact that a single instance of the thread should be running (for example using a singleton instance that is cleared upon finishing the task)

街角迷惘 2024-08-01 15:48:16

您是否可以不使用 SwingWorker 来使用 ThreadPoolExecutor 来执行客户端-服务器通信,然后调用 SwingUtilities.invokeLater 来使用结果更新 UI? 对我来说,这似乎更干净一些,并且可以确保事件和 UI 更新仍然按顺序处理。

当您向执行者提交任务时,您可以保留对其 Future 实例的引用,以便您可以在需要时取消任务。

Instead of using a SwingWorker could you not use a ThreadPoolExecutor to perform the client-server communication and then call SwingUtilities.invokeLater to update the UI with the result? This to me seems a bit cleaner and would ensure that events and UI updates are still processed in order.

As you submit tasks to your executor you could keep a reference to their Future instances so that you can cancel the task if needed.

燕归巢 2024-08-01 15:48:16

让我看看我是否正确理解了这个问题。 您有一个 FIFO 任务队列,只有最旧的任务正在运行。 每个任务完成后都需要更新 UI。 但是,如果某个用户事件到来,则所有任务都需要取消——也就是说,正在运行的任务需要取消,尚未运行的任务需要从队列中删除。 是对的吗?

假设是这样,我不会使用 SwingWorker 因为您只需要一个工作线程,而不是每个任务一个。 FutureTask 应该足够了(假设您重写 done() 以对 SwingUtilities.invokeLater() 进行必要的调用并进行 UI 更新)。

如果您取消 FutureTask,那么即使它的 run() 方法被调用,它也不会执行任何操作。 因此,您可以安全地将 FutureTask 提交给 ExecutorService,因为知道即使执行程序尝试运行它们,取消也会起作用。

我怀疑一个足够好的解决方案就是保留可能需要取消的所有 FutureTasks 的列表,并在用户事件到来时将它们全部取消。ExecutorService code> 仍会尝试运行它们,但基本上是无操作。 您需要确保已完成的任务从列表中删除,并且需要确保列表以线程安全的方式更新和使用(可能来自将任务放在 ExecutorService 上的同一线程) ),但这应该不会太难。

我在短短一个小时内就把下面的代码敲出来了,我不敢打赌它是正确的,但你明白了。 :)

/** Untested code! Use at own risk. */
public class SwingTaskExecutor {

    // ////////////////////////////////////////////////////////////
    // Fields

    private final ExecutorService execSvc = Executors.newFixedThreadPool(1);

    private final Lock listLock = new ReentrantLock();
    private final List<ManagedSwingTask<?>> activeTasks = 
            new ArrayList<ManagedSwingTask<?>>();

    // ////////////////////////////////////////////////////////////
    // Public methods

    public <T> Future<T> submit(SwingTask<T> task) {
        ManagedSwingTask<T> managedTask = new ManagedSwingTask<T>(task);
        addToActiveTasks(managedTask);
        execSvc.submit(managedTask);
        return managedTask;
    }

    public void cancelAllTasks() {
        listLock.lock();
        try {
            for (ManagedSwingTask<?> t: activeTasks) {
                t.cancel(true);
            }
            activeTasks.clear();
        } finally {
            listLock.unlock();
        }
    }

    // ////////////////////////////////////////////////////////////
    // Private methods

    private <T> void addToActiveTasks(ManagedSwingTask<T> managedTask) {
        listLock.lock();
        try {
            activeTasks.add(managedTask);
        } finally {
            listLock.unlock();
        }
    }

    // ////////////////////////////////////////////////////////////
    // Helper classes

    private class ManagedSwingTask<T> extends FutureTask<T> {

        private final SwingTask<T> task;

        ManagedSwingTask(SwingTask<T> task) {
            super(task);
            this.task = task;
        }

        @Override
        public void cancel(boolean mayInterruptIfRunning) {
            try {
                task.cancel();
            } finally {
                super.cancel(mayInterruptIfRunning);
            }
        }

        @Override
        protected void done() {
            removeFromActiveTasks();
            updateUIIfDone();
        }

        private void removeFromActiveTasks() {
            listLock.lock();
            try {
                activeTasks.remove(this);
            } finally {
                listLock.unlock();
            }
        }

        private void updateUIIfDone() {
            if (isDone()) {
                SwingUtilities.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        task.updateUI();
                    }
                });
            }
        }
    }

    public static interface SwingTask<T> extends Callable<T> {

        /** Called from the EDT if task completes successfully */
        void updateUI();

        /** Hook in case there's task-specific cancellation to be done*/
        void cancel();
    }
}

无论如何,类似的事情。

如果您想更加确定,则可以关闭并替换 ExecutorService,但这可能没有必要。

Let me see if I understand the problem correctly. You have a FIFO queue of tasks, only the oldest of which is running. Each task needs to update the UI when it's done. But if a certain user event comes in, all tasks need to be cancelled -- that is, running tasks need to be cancelled, and not-yet-running tasks need to be removed from the queue. Is that right?

Assuming it is, I wouldn't use SwingWorker since you only need one worker thread, not one per task. FutureTask should be enough (assuming you override done() to make the necessary call to SwingUtilities.invokeLater() and do the UI update).

If you cancel a FutureTask, then even if its run() method gets called, it won't do anything. So you can submit FutureTasks safely to a ExecutorService knowing that cancellation will work even if the executor tries to run them.

I suspect that a good-enough solution would just be to keep a list of all FutureTasks that might need to be cancelled, and cancel them all when the user event comes in. The ExecutorService will still try to run them but it'll basically be a no-op. You need to make sure completed tasks are removed from the list, and you need to make sure the list is updated and used in a thread-safe way (probably from the same thread that puts tasks on the ExecutorService), but that shouldn't be too hard.

I bashed the code below out in just an hour and I wouldn't bet on it being correct, but you get the idea. :)

/** Untested code! Use at own risk. */
public class SwingTaskExecutor {

    // ////////////////////////////////////////////////////////////
    // Fields

    private final ExecutorService execSvc = Executors.newFixedThreadPool(1);

    private final Lock listLock = new ReentrantLock();
    private final List<ManagedSwingTask<?>> activeTasks = 
            new ArrayList<ManagedSwingTask<?>>();

    // ////////////////////////////////////////////////////////////
    // Public methods

    public <T> Future<T> submit(SwingTask<T> task) {
        ManagedSwingTask<T> managedTask = new ManagedSwingTask<T>(task);
        addToActiveTasks(managedTask);
        execSvc.submit(managedTask);
        return managedTask;
    }

    public void cancelAllTasks() {
        listLock.lock();
        try {
            for (ManagedSwingTask<?> t: activeTasks) {
                t.cancel(true);
            }
            activeTasks.clear();
        } finally {
            listLock.unlock();
        }
    }

    // ////////////////////////////////////////////////////////////
    // Private methods

    private <T> void addToActiveTasks(ManagedSwingTask<T> managedTask) {
        listLock.lock();
        try {
            activeTasks.add(managedTask);
        } finally {
            listLock.unlock();
        }
    }

    // ////////////////////////////////////////////////////////////
    // Helper classes

    private class ManagedSwingTask<T> extends FutureTask<T> {

        private final SwingTask<T> task;

        ManagedSwingTask(SwingTask<T> task) {
            super(task);
            this.task = task;
        }

        @Override
        public void cancel(boolean mayInterruptIfRunning) {
            try {
                task.cancel();
            } finally {
                super.cancel(mayInterruptIfRunning);
            }
        }

        @Override
        protected void done() {
            removeFromActiveTasks();
            updateUIIfDone();
        }

        private void removeFromActiveTasks() {
            listLock.lock();
            try {
                activeTasks.remove(this);
            } finally {
                listLock.unlock();
            }
        }

        private void updateUIIfDone() {
            if (isDone()) {
                SwingUtilities.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        task.updateUI();
                    }
                });
            }
        }
    }

    public static interface SwingTask<T> extends Callable<T> {

        /** Called from the EDT if task completes successfully */
        void updateUI();

        /** Hook in case there's task-specific cancellation to be done*/
        void cancel();
    }
}

Something like that, anyway.

If you want to be doubly sure, you could then shut down and replace the ExecutorService, but that's probably not necessary.

谁许谁一生繁华 2024-08-01 15:48:15

创建仅执行最后传入的 Runnable 的单线程 ThreadPoolExecutor 的一种方法是子类化合适的队列类并重写所有添加方法以在添加新的可运行之前清除队列。 然后将该队列设置为 ThreadPoolExecutor 的工作队列。

One way to create a Single thread ThreadPoolExecutor that only executes last incoming Runnable is to subclass a suitable queue class and override all adding methods to clear the queue before adding the new runnable. Then set that queue as the ThreadPoolExecutor's work queue.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文