javafx.concurrent.Task:如何分离库和GUI?

问题描述 投票:0回答:1

我有一个 JavaFX 应用程序,其中一项功能涉及搜索(大型)二进制数据以查找特定模式。为了保持 GUI 的响应能力,我创建了一个并发任务并将其放在不同的线程上:

public void search(SearchPattern pattern) {
    
    Task<ObservableList<...>> task = new Task<>() {
        @Override protected ObservableList<...> call() throws Exception {

            try {                    
                for(int i=0;i<....size();i++) {
                    if (isCancelled()) {
                        break;
                    }
                    if(i%50000 == 0) {
                        updateProgress(i, entries.size());
                    }
                    if(pattern.matches(entries.get(i))) {
                        // do stuff
                    }                        
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                // IO cleanup
            }
            return FXCollections.observableArrayList(...);
        }
    };

    progressBar.progressProperty().bind(task.progressProperty());

    task.setOnSucceeded(e -> {
        unregisterRunningTask(task);
        searchResults = task.getValue();
    });

    Thread thread = new Thread(task);
    registerRunningTask(task);
    thread.setDaemon(false);
    thread.start();
}

这工作得很好,但是将(库)功能与 GUI 代码混合在一起。特别是,我想将搜索代码放入一个单独的库中,为该功能编写测试用例 - 完全自动化,无需 GUI - 当然,当从命令行进行测试时,不需要单独的线程。现在,我只能将

pattern.matches(entries.get(i))
放入该库中。更干净的方法是有一个函数

searchpattern(filename) {
    for(int=0;i< ... ;i++) {
       ...
    }
    return observableArrayList(...);
}

在包含完整循环的库中,编写我的测试用例和基准测试,然后以某种方式将该函数包含在 GUI 代码中。分离这段代码时我面临的问题是:

updateProgress()
需要对任务的引用。当然,我总是可以在尝试实现测试用例时创建一个任务,即即使从非 GUI 上下文调用
searchpattern(filename)
时也是如此。我可以简单地忽略代码中的任何进度更新,但这会造成非常糟糕的用户体验。

是否有其他方法可以使用某些抽象层从(可能)长时间运行的函数传递更新,这使得该函数在库中从(线程)GUI 上下文和非线程命令行上下文都很容易?

multithreading javafx task
1个回答
0
投票

这里有两种方法。为了让事情更具体,假设我们有一个类在列表上实现一个可能长时间运行的进程:

public abstract class ListProcessor<T> {

    private final List<T> items ;

    public ListProcessor(List<T> items) {
        this.items = items ;
    }

    public void process() {
        for (int i = 0 ; i < items.size(); i++) {
            processItem(items.get(i));
        }
    }

    public abstract processItem(T item);
}

(我在这里对列表在处理过程中未被修改等做出了大量假设)

第一种方法是如@Slaw在评论中所描述的。在这种方法中,您的外部库类保留一个侦听器列表,当处理的项目数发生变化时,这些侦听器会收到通知。您可以简单地使用

DoubleConsumer
来实现此目的。例如:

public abstract class ListProcessor<T> {

    private final List<T> items ;

    private final List<DoubleConsumer> listeners = new ArrayList<>();

    public ListProcessor(List<T> items) {
        this.items = items ;
    }

    public void addListener(DoubleConsumer listener) {
        listeners.add(listener);
    }

    public void removeListener(DoubleConsumer listener) {
        listeners.remove(listener);
    }

    public void process() {
        int n = items.size();
        for (int i = 0 ; i < n; i++) {
            processItem(items.get(i));
            listeners.forEach(l -> l.accept(1.0 * i / n));
        }
    }

    public abstract processItem(T item);
}

您可以在任务中使用它,如下所示:

List<Widget> widgets = ... ;
ListProcessor<Widget> widgetProcessor = new WidgetProcessor(widgets);

Task<Void> widgetTask = new Task<>() {
    @Override
    protected Void call() {
        DoubleConsumer progressUpdater = prog -> updateProgress(prog, 1.0);
        widgetProcessor.addListener(progressUpdater);
        widgetProcessor.process();
        widgetProcessor.removeListener(progressUpdater);
        updateProgress(1.0, 1.0);
        return null;
    }
};

progressBar.progressProperty().bind(widgetTask.progressProperty());
// ...
executor.execute(widgetTask);

这依赖于您的库类实现侦听器通知机制。如果您不想这样做,或者您正在使用某些未实现它的第三方库,则可以轮询处理对象以了解其当前进度。我会从

AnimationTimer
进行轮询,尽管其他方法也是可能的。最低要求(据我所知)是处理对象有一种方法以线程安全的方式报告其进度(因为您将从不同的线程轮询到正在更新它的线程) . 所以,例如:

public abstract class ListProcessor<T> { private final List<T> items ; private volatile double progress = 0.0; public ListProcessor(List<T> items) { this.items = items ; } public void process() { int n = items.size(); for (int i = 0 ; i < n; i++) { processItem(items.get(i)); progress = (1.0 * i / n); } } public double getProgress() { return progress; } public abstract processItem(T item); }

然后

List<Widget> widgets = ... ; ListProcessor<Widget> widgetProcessor = new WidgetProcessor(widgets); Task<Void> widgetTask = new Task<>() { @Override protected Void call() { AnimationTimer progressUpdater = new AnimationTimer() { @Override public void handle(long now) { updateProgress(widgetProcessor.getProgress(), 1.0); } }; progressUpdater.start(); widgetProcessor.process(); progressUpdater.stop(); updateProgress(1.0, 1.0); return null; } }; progressBar.progressProperty().bind(widgetTask.progressProperty()); // ... executor.execute(widgetTask);

	
© www.soinside.com 2019 - 2024. All rights reserved.