在开发多线程Javafx应用程序时需要注意哪些事项?

时间:2014-10-04 10:11:07

标签: java multithreading java-8 javafx-8

我是Javafx的新手并使用它开发IDE。我面对JavaFX的问题是我必须使用Platform.runLater()来反映来自其他线程的GUI的变化。正如我的IDE开发使用多个线程来保持最新信息并使用Platform.runLater()使应用程序无响应。有时后台进程必须打印数百万行的输出,我认为当多个线程尝试执行相同操作时会导致问题。我试图放一个计数器,这样如果输出大于250000行,它将在250000行之后打印输出,否则它将在完成线程后立即打印,即使在这种情况下,如果两个或多个线程尝试执行{{ 1}}(还有其他线程创建带有复选框项的树并反映实时值)应用程序挂起但后台的所有内容都保持正常运行,甚至应用程序也不会抛出任何异常。在普通的java swing应用程序中,我没有遇到任何类似的问题。所以,我正在寻求解决这些问题的指导。有人可以给我PRO解决类似问题的技巧吗? :)

编辑@jewelsea

的请求

我试图让示例代码尽可能简单

FxUI.java

Platform.runLater()

simpleThread.java

 public class FxUI extends Application {
 public static TextArea outputArea;

@Override
public void start(Stage primaryStage) {
    outputArea= new TextArea();
    Button btn = new Button();

    btn.setText("Start Appending Text To Text Area");
    btn.setOnAction(new EventHandler<ActionEvent>() {

        @Override
        public void handle(ActionEvent event) {
          Thread r=new Thread( new Runnable() {

               @Override
               public void run() {
              for (int i = 0; i < 10; i++) {
                Thread t= new Thread(new simpleThread(i));
            t.start();
                try {
                    Thread.sleep(1000);
                    System.out.println("Thread Awake");
                } catch (InterruptedException ex) {
                    Logger.getLogger(FxUI.class.getName()).log(Level.SEVERE, null, ex);
                }
            }  }
           });
          r.start();
        }
    });

    VBox root = new VBox(30);
    outputArea.setWrapText(true);
    outputArea.setPrefHeight(400);
    root.getChildren().add(outputArea);
    root.getChildren().add(btn);

    Scene scene = new Scene(root, 500, 500);

    primaryStage.setTitle("Hello World!");
    primaryStage.setScene(scene);
    primaryStage.show();
}

/**
 * @param args the command line arguments
 */
public static void main(String[] args) {
    launch(args);
}

}

2 个答案:

答案 0 :(得分:4)

我认为这里的第一个真正的问题是你的代码非常低效。在循环中构建字符串是一件非常糟糕的事情:您创建一个新对象并每次复制所有字符。此外,每次更新文本区域时,您都要复制整个现有文本,通过连接其他内容创建另一个String,然后用新内容替换所有现有内容。字符串连接将以二次方式运行(因为每次增加字符串的长度)并且您将导致Java的字符串实习过程中的混乱。

另外,请注意,除FX应用程序线程外,您不应该在任何地方读取场景图中的节点状态,因此您的行

        content = FxUI.outputArea.getText() + "\n" + output;

不是线程安全的。

通常,要在循环中构建字符串,您应该使用StringBuilder来构建字符串内容。如果您使用的是TextArea,则会使用appendText(...)方法来更新它。

更新以下评论中的讨论:

做出这些一般性评论后,进行这些改进并不能让您达到性能可接受的状态。我的观察结果是TextArea即使在线程完成后也很难响应用户输入。问题是(我猜)你有大量的数据实际上与场景图的“实时”部分相关联。

这里更好的选择可能是使用虚拟控件(如ListView)来显示数据。这些只有可见部分的单元格,并在用户滚动时重复使用它们。这是一个例子。我添加了选择和复制到剪贴板功能,因为这是您错过从TextAreaListView的主要内容。 (请注意,如果您选择了大量内容,则String.join()方法运行速度非常慢。您可能需要为此创建后台任务,并且如果重要,则可以使用阻止对话框来显示其进度。)< / p>

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.concurrent.Task;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ListView;
import javafx.scene.control.SelectionMode;
import javafx.scene.input.Clipboard;
import javafx.scene.input.ClipboardContent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

public class BigListBackgroundThreadDemo extends Application {

    private static final int NUM_ITERATIONS = 10_000 ;
    private static final int NUM_THREADS_PER_CALL = 5 ;

    @Override
    public void start(Stage primaryStage) {
        ListView<String> data = new ListView<>();
        data.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
        Button startButton = new Button("Start");
        Button selectAllButton = new Button("Select All");
        Button selectNoneButton = new Button("Clear Selection");
        Button copyToClipboardButton = new Button("Copy to clipboard");
        copyToClipboardButton.disableProperty().bind(Bindings.isEmpty(data.getSelectionModel().getSelectedItems()));

        AtomicInteger threadCount = new AtomicInteger();
        ExecutorService exec = Executors.newFixedThreadPool(5, r -> {
            Thread t = new Thread(r);
            t.setDaemon(true);
            return t ;
        });

        startButton.setOnAction(event -> {
            exec.submit(() -> {
                for (int i=0; i < NUM_THREADS_PER_CALL; i++) {
                    exec.submit(createTask(threadCount, data));
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException exc) {
                        throw new Error("Unexpected interruption", exc);
                    }
                }
            });
        });

        selectAllButton.setOnAction(event -> {
            data.getSelectionModel().selectAll();
            data.requestFocus();
        });
        selectNoneButton.setOnAction(event -> {
            data.getSelectionModel().clearSelection();
            data.requestFocus();
        });

        copyToClipboardButton.setOnAction(event -> {
            ClipboardContent clipboardContent = new ClipboardContent();
            clipboardContent.putString(String.join("\n", data.getSelectionModel().getSelectedItems()));
            Clipboard.getSystemClipboard().setContent(clipboardContent);
        });

        HBox controls = new HBox(5, startButton, selectAllButton, selectNoneButton, copyToClipboardButton);
        controls.setAlignment(Pos.CENTER);
        controls.setPadding(new Insets(5));

        BorderPane root = new BorderPane(data, null, null, controls, null);


        Scene scene = new Scene(root, 800, 600);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    private Task<Void> createTask(AtomicInteger threadCount, ListView<String> target) {
        return new Task<Void>() {
            @Override
            public Void call() throws Exception {
                int count = threadCount.incrementAndGet();
                AtomicBoolean pending = new AtomicBoolean(false);
                BlockingQueue<String> messages = new LinkedBlockingQueue<>();
                for (int i=0; i < NUM_ITERATIONS; i++) {
                    messages.add("Thread number: "+count + "\tLoop counter: "+i);
                    if (pending.compareAndSet(false, true)) {
                        Platform.runLater(() -> {
                            pending.set(false);
                            messages.drainTo(target.getItems());
                            target.scrollTo(target.getItems().size()-1);
                        });
                    }
                }

                return null ;
            }
        };
    }


    public static void main(String[] args) {
        launch(args);
    }
}

答案 1 :(得分:1)

在JavaFX中,您必须在运行ServiceTask中执行后台处理。通过这样做,你将不会冻结你的GUI线程

快速示例,如果您希望将String作为流程的返回值。

服务:

    public class MyService extends Service<String> {
    @Override
    protected Task<String> createTask() {
        return new Task<String>() {
            @Override
            protected String call() throws Exception {
                //Do your heavy stuff
                return "";
            }
        };
    }
}

您想要使用您的服务:

    final MyService service = new MyService();

    service.setOnSucceeded(e -> {
        //your service finish with no problems
        service.getValue(); //get the return value of your service
    });

    service.setOnFailed(e -> {
        //your service failed
    });

    service.restart();

对于不同的状态,您还有其他方法,如setOnFailed。所以实现你需要的。 您也可以监控此服务,但我让您阅读此文档。这很简单。

您还应该阅读JavaFX concurency