为什么WebEngine的工作线程永远不会完成?

时间:2017-12-28 15:50:49

标签: java javafx javafx-webengine

我的目标是从Web服务器加载文档,然后解析其DOM以获取特定内容。加载DOM是我的问题。

我正在尝试使用javafx.scene.web.WebEngine,因为这似乎应该能够执行所有必要的机制,包括javascript执行,这可能会影响最终的DOM。

加载文档时,它似乎陷入RUNNING状态并且永远不会达到SUCCEEDED状态,我认为在从WebEngine.getDocument()访问DOM之前需要该状态。

无论是从URL还是文字内容加载(在此最小示例中使用),都会发生这种情况。

任何人都可以看到我做错了什么或误解了吗?

提前感谢您的帮助。

import java.util.concurrent.ExecutionException;
import org.w3c.dom.Document;
import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.concurrent.Worker;
import javafx.embed.swing.JFXPanel;
import javafx.scene.web.WebEngine;

public class WebEngineProblem {
    private static Task<WebEngine> getEngineTask() {
        Task<WebEngine> task = new Task<>() {
            @Override
            protected WebEngine call() throws Exception {
                WebEngine webEngine = new WebEngine();
                final Worker<Void> loadWorker = webEngine.getLoadWorker();
                loadWorker.stateProperty().addListener((obs, oldValue, newValue) -> {
                    System.out.println("state:" + newValue);
                    if (newValue == State.SUCCEEDED) {
                        System.out.println("finished loading");
                    }    
                });
                webEngine.loadContent("<!DOCTYPE html>\r\n" + "<html>\r\n" + "<head>\r\n" + "<meta charset=\"UTF-8\">\r\n"
                    + "<title>Content Title</title>\r\n" + "</head>\r\n" + "<body>\r\n" + "<p>Body</p>\r\n" + "</body>\r\n"
                    + "</html>\r\n");
                State priorState = State.CANCELLED; //should never be CANCELLED
                double priorWork = Double.NaN;
                while (loadWorker.isRunning()) {
                    final double workDone = loadWorker.getWorkDone();
                    if (loadWorker.getState() != priorState || priorWork != workDone) {
                        priorState = loadWorker.stateProperty().getValue();
                        priorWork = workDone;
                        System.out.println(priorState + " " + priorWork + "/" + loadWorker.getTotalWork());
                    }
                    Thread.sleep(1000);
                }
                return webEngine;
            }
        };
        return task;
    }

    public static void main(String[] args) {
        new JFXPanel(); // Initialise the JavaFx Platform
        WebEngine engine = null;
        Task<WebEngine> task = getEngineTask();
        try {
            Platform.runLater(task);
            Thread.sleep(1000); 
            engine = task.get(); // Never completes as always RUNNING
        }
        catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
        // This code is never reached as the content never completes loading
        // It would fail as it's not on the FX thread.
        Document doc = engine.getDocument();
        String content = doc.getTextContent();
        System.out.println(content);
    }

}

1 个答案:

答案 0 :(得分:1)

对于Worker的{​​{1}}属性的更改将在FX应用程序线程上发生,即使该工作程序在后台线程上运行。 (JavaFX属性本质上是单线程的。)在加载Web引擎内容的线程的实现中,有一个调用state来改变worker的状态。

由于您的任务阻止,直到工作人员的状态发生变化,并且由于您在FX应用程序线程上运行任务,因此您基本上已经使FX应用程序线程死锁:直到负载工作者状态的更改才会发生。您的任务完成(因为它在同一个线程上运行),并且您的任务无法完成,直到状态发生变化(这就是您编写任务的目的)。

阻止FX应用程序线程基本上总是错误的。相反,您应该阻止另一个线程,直到您想要的条件为真(创建Web引擎并加载线程完成),然后执行下一个要发生的事情(如果需要,再次使用Platform.runLater(...)在FX应用程序线程上执行。

以下是我认为您正在尝试做的事情的示例:

Platform.runLater(...)