【问题标题】:Editable queue of tasks running in background thread在后台线程中运行的可编辑任务队列
【发布时间】:2015-12-31 02:12:30
【问题描述】:

我知道这个问题已经回答了很多次,但我很难理解它是如何工作的。

所以在我的应用程序中,用户必须能够选择将添加到队列中的项目(使用ObservableList<Task> 显示在ListView 中),并且每个项目都需要由ExecutorService 按顺序处理。

此外,该队列应该是可编辑的(更改顺序并从列表中删除项目)。

private void handleItemClicked(MouseEvent event) {
    if (event.getClickCount() == 2) {
        File item = listView.getSelectionModel().getSelectedItem();
        Task<Void> task = createTask(item);
        facade.getTaskQueueList().add(task); // this list is bound to a ListView, where it can be edited
        Future result = executor.submit(task); 
        // where executor is an ExecutorService of which type?

        try {
            result.get();
        } catch (Exception e) {
            // ...
        }
    }
}

executor = Executors.newFixedThreadPool(1) 尝试过,但我无法控制队列。
我阅读了有关 ThreadPoolExecutor 和队列的信息,但我很难理解它,因为我对并发还很陌生。

我需要在后台线程中运行该方法handleItemClicked,以便 UI 不会冻结,我怎样才能做到最好?

总结:如何实现一个任务队列,可以编辑,并由后台线程按顺序处理?

请帮我解决

编辑 使用 vanOekel 的 SerialTaskQueue 类帮助了我,现在我想将任务列表绑定到我的 ListView

ListProperty<Runnable> listProperty = new SimpleListProperty<>();
listProperty.set(taskQueue.getTaskList()); // getTaskList() returns the LinkedList from SerialTaskQueue
queueListView.itemsProperty().bind(listProperty); 

显然这不起作用,因为它需要一个 ObservableList。有一种优雅的方法吗?

【问题讨论】:

    标签: java multithreading queue executorservice


    【解决方案1】:

    我能想到的最简单的解决方案是在执行器之外维护任务列表,并使用回调为执行器提供下一个任务(如果可用)。不幸的是,它涉及到任务列表上的同步和一个AtomicBoolean 来指示正在执行的任务。

    回调只是一个Runnable,它包装了要运行的原始任务,然后“回调”以查看是否有另一个任务要执行,如果有,则使用(后台)执行器执行它。

    需要同步以使任务列表保持有序并处于已知状态。任务列表可以同时被两个线程修改:通过在执行器(后台)线程中运行的回调和通过 UI 前台线程执行的handleItemClicked 方法。这反过来意味着,例如,当任务列表为空时,永远无法准确知道。为了使任务列表保持有序并处于已知的固定状态,需要对任务列表进行同步。

    这仍然留下一个模棱两可的时刻来决定何时准备好执行任务。这就是AtomicBoolean 的用武之地:一个值集总是立即可用并被任何其他线程读取,compareAndSet 方法将始终确保只有一个线程获得“OK”。

    将同步和AtomicBoolean 的使用结合起来,可以创建一个具有“临界区”的方法,该方法可由前台线程和后台线程同时调用以触发新任务的执行如果可能的话。以下代码的设计和设置方式使得可以存在一种这样的方法 (runNextTask)。让并发代码中的“关键部分”尽可能简单和明确是一种很好的做法(这通常会导致高效的“关键部分”)。

    import java.util.*;
    import java.util.concurrent.*;
    import java.util.concurrent.atomic.AtomicBoolean;
    
    public class SerialTaskQueue {
    
        public static void main(String[] args) {
    
            ExecutorService executor = Executors.newSingleThreadExecutor();
            // all operations on this list must be synchronized on the list itself.
            SerialTaskQueue tq = new SerialTaskQueue(executor);
            try {
                // test running the tasks one by one
                tq.add(new SleepSome(10L));
                Thread.sleep(5L);
                tq.add(new SleepSome(20L));
                tq.add(new SleepSome(30L));
    
                Thread.sleep(100L);
                System.out.println("Queue size: " + tq.size()); // should be empty
                tq.add(new SleepSome(10L));
    
                Thread.sleep(100L);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                executor.shutdownNow();
            }
        }
    
        // all lookups and modifications to the list must be synchronized on the list.
        private final List<Runnable> tasks = new LinkedList<Runnable>();
        // atomic boolean used to ensure only 1 task is executed at any given time
        private final AtomicBoolean executeNextTask = new AtomicBoolean(true);
        private final Executor executor;
    
        public SerialTaskQueue(Executor executor) {
            this.executor = executor;
        }
    
        public void add(Runnable task) {
    
            synchronized(tasks) { tasks.add(task); }
            runNextTask();
        }
    
        private void runNextTask() {
            // critical section that ensures one task is executed.
            synchronized(tasks) {
                if (!tasks.isEmpty()
                        && executeNextTask.compareAndSet(true, false)) {
                    executor.execute(wrapTask(tasks.remove(0)));
                }
            }
        }
    
        private CallbackTask wrapTask(Runnable task) {
    
            return new CallbackTask(task, new Runnable() {
                @Override public void run() {
                    if (!executeNextTask.compareAndSet(false, true)) {
                        System.out.println("ERROR: programming error, the callback should always run in execute state.");
                    }
                    runNextTask();
                }
            });
        }
    
        public int size() {
            synchronized(tasks) { return tasks.size(); }
        }
    
        public Runnable get(int index) {
            synchronized(tasks) { return tasks.get(index); }
        }
    
        public Runnable remove(int index) {
            synchronized(tasks) { return tasks.remove(index); }
        }
    
        // general callback-task, see https://stackoverflow.com/a/826283/3080094
        static class CallbackTask implements Runnable {
    
            private final Runnable task, callback;
    
            public CallbackTask(Runnable task, Runnable callback) {
                this.task = task;
                this.callback = callback;
            }
    
            @Override public void run() {
                try {
                    task.run();
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    try {
                        callback.run();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
        // task that just sleeps for a while
        static class SleepSome implements Runnable {
    
            static long startTime = System.currentTimeMillis();
    
            private final long sleepTimeMs;
            public SleepSome(long sleepTimeMs) {
                this.sleepTimeMs = sleepTimeMs;
            }
            @Override public void run() {
                try { 
                    System.out.println(tdelta() + "Sleeping for " + sleepTimeMs + " ms.");
                    Thread.sleep(sleepTimeMs);
                    System.out.println(tdelta() + "Slept for " + sleepTimeMs + " ms.");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
    
            private String tdelta() { return String.format("% 4d ", (System.currentTimeMillis() - startTime)); }
        }
    }
    

    更新:如果需要串行执行任务组,请查看调整后的实现here

    【讨论】:

    • 谢谢,这对我有帮助。现在的另一个问题是如何将List&lt;Runnable&gt; tasks = new LinkedList&lt;Runnable&gt;(); 列表绑定到我的ListView
    • @lenny42 我对ObservableList 不熟悉,但是你可以反过来做吗?使用类似tasks = FXCollections.observableArrayList()(见stackoverflow.com/a/26195354/3080094)的东西?
    • @lenny42 或者使用ModifiableObservableListBase 并使用SerialTaskQueue 的更新版本作为javadoc 示例中所示的委托?
    • 使用tasks = FXCollections.observableArrayList() 效果很好,谢谢!
    猜你喜欢
    • 1970-01-01
    • 2018-07-02
    • 2016-02-26
    • 1970-01-01
    • 2015-10-05
    • 1970-01-01
    • 2021-11-17
    • 2018-02-05
    • 1970-01-01
    相关资源
    最近更新 更多