在多个UI线程下运行Java应用程序。

huangapple go评论79阅读模式
英文:

Running java Application with Multiple UI Threads

问题

这是主类:

import java.io.*;

public class ClientController {
    public static void main(String args[]) throws IOException {
        ParallelClient c1 = new ParallelClient("aaaa@gmail.com");
        ParallelClient c2 = new ParallelClient("bbbb@gmail.com");
        c1.start();
        c2.start();
    }
}

这是 ParallelClient 类:

import ...

public class ParallelClient extends Thread {
    private String user;

    public ParallelClient(String user) {
        this.user = user;
    }

    public void run() {
        ClientApp app = new ClientApp();
        try {
            app.start(new Stage());
        } catch (Exception e) {
            e.printStackTrace();
        }
        ...
    }
    ...
}

这是 ClientApp 类,用于设置新窗口:

import ...

public class ClientApp extends Application {

    @Override
    public void start(Stage stage) throws Exception {
        try {
            Parent root = FXMLLoader.load(getClass().getResource("ui/client-management.fxml"));
            stage.setTitle("ClientMail");
            stage.setScene(new Scene(root, 1080, 720));
            stage.show();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

当我尝试运行代码时,出现了以下问题,我无法理解如何解决:

Exception in thread "Thread-0" Exception in thread "Thread-1" java.lang.NoClassDefFoundError: Could not initialize class javafx.stage.Screen
	at javafx.stage.Window.<init>(Window.java:1439)
	at javafx.stage.Stage.<init>(Stage.java:252)
	at javafx.stage.Stage.<init>(Stage.java:240)
	at model.ParallelClient.run(ParallelClient.java:25)
java.lang.ExceptionInInitializerError
	at javafx.stage.Window.<init>(Window.java:1439)
	at javafx.stage.Stage.<init>(Stage.java:252)
	at javafx.stage.Stage.<init>(Stage.java:240)
	at model.ParallelClient.run(ParallelClient.java:25)
Caused by: java.lang.IllegalStateException: This operation is permitted on the event thread only; currentThread = Thread-1
	at com.sun.glass.ui.Application.checkEventThread(Application.java:441)
	at com.sun.glass.ui.Screen.setEventHandler(Screen.java:369)
	at com.sun.javafx.tk.quantum.QuantumToolkit.setScreenConfigurationListener(QuantumToolkit.java:728)
	at javafx.stage.Screen.<clinit>(Screen.java:74)
	... 4 more
英文:

I'm trying to coding a very simple Client-Server Email project in java.
I've already code the communication between client and server using socket and now I'm tryng to code some test which includes also a very simple UI.
My idea is to create many threads as many clients I have and I want that every sigle thread starts opening a simple UI window created with Java FX but I have some problems.

This is the main class:

import java.io.*;

public class ClientController{
    public static void main(String args[]) throws IOException {
        ParallelClient c1=new ParallelClient(&quot;aaaa@gmail.com&quot;);
        ParallelClient c2=new ParallelClient(&quot;bbbb@gmail.com&quot;);
        c1.start();
        c2.start();
    }
}

This is the ParallelClient class:

import ...

public class ParallelClient extends Thread{
    private String user;

    public ParallelClient(String user){
        this.user=user;
    }

    public void run(){
        ClientApp app=new ClientApp();
        try {
            app.start(new Stage());
        } catch (Exception e) {
            e.printStackTrace();
        }
        ...
    }
    ...
}

And this is the ClientApp class which set the new window:

import ...

public class ClientApp extends Application {

    @Override
    public void start(Stage stage) throws Exception {
        try {
            Parent root = FXMLLoader.load(getClass().getResource(&quot;ui/client-management.fxml&quot;));
            stage.setTitle(&quot;ClientMail&quot;);
            stage.setScene(new Scene(root, 1080, 720));
            stage.show();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

When I try to run the code I get the followung problem and I can't understand how to fix it:

Exception in thread &quot;Thread-0&quot; Exception in thread &quot;Thread-1&quot; java.lang.NoClassDefFoundError: Could not initialize class javafx.stage.Screen
	at javafx.stage.Window.&lt;init&gt;(Window.java:1439)
	at javafx.stage.Stage.&lt;init&gt;(Stage.java:252)
	at javafx.stage.Stage.&lt;init&gt;(Stage.java:240)
	at model.ParallelClient.run(ParallelClient.java:25)
java.lang.ExceptionInInitializerError
	at javafx.stage.Window.&lt;init&gt;(Window.java:1439)
	at javafx.stage.Stage.&lt;init&gt;(Stage.java:252)
	at javafx.stage.Stage.&lt;init&gt;(Stage.java:240)
	at model.ParallelClient.run(ParallelClient.java:25)
Caused by: java.lang.IllegalStateException: This operation is permitted on the event thread only; currentThread = Thread-1
	at com.sun.glass.ui.Application.checkEventThread(Application.java:441)
	at com.sun.glass.ui.Screen.setEventHandler(Screen.java:369)
	at com.sun.javafx.tk.quantum.QuantumToolkit.setScreenConfigurationListener(QuantumToolkit.java:728)
	at javafx.stage.Screen.&lt;clinit&gt;(Screen.java:74)
	... 4 more

答案1

得分: 4

应用程序的结构存在几个问题,就你在问题中发布的内容而言。

Application 类代表整个应用程序的生命周期,通过调用其 init()start()stop() 方法进行管理。在整个应用程序中应该只有一个 Application 实例,通常此实例是由 JavaFX 启动机制创建的,因此不应自己实例化 Application 子类。

JavaFX 应用程序需要启动 JavaFX 运行时,其中包括启动 JavaFX 应用程序线程。这是通过调用静态的 Application.launch() 方法来完成的,必须只调用一次。launch() 方法启动 JavaFX 运行时,创建 Application 类的实例,调用 init(),然后在 FX 应用程序线程上调用 start()。(在 JavaFX 9 及更高版本中,你还可以通过调用 Platform.startup() 来启动运行时,但这种情况很少见。)

注意,在你的应用程序中,没有调用 Application.launch()(或 Platform.startup()),因此 JavaFX 运行时从未启动。

某些操作只能在 FX 应用程序线程上执行。这些操作包括创建 StageScene,以及对已显示的 UI 元素的任何属性进行修改。因此,不能在单独的线程中“运行每个客户端”。这是异常的原因:你试图在不是 FX 应用程序线程的线程上创建新的 Stage

每个客户端不需要新线程来显示 UI(正如上面所述,也不能这样做)。你可能确实需要在单独的线程上执行每个客户端与服务器的通信(因为这些操作需要很长时间,不应阻塞 FX 应用程序线程)。你可以通过为每个客户端的服务器通信创建新线程,或者使用共享的执行器服务,以便每个客户端可以从线程池中获取线程(这可能是首选方法)。

因此,你的结构应该类似于这样:

public class Client {

    private Parent ui;
    private ExecutorService exec; // 用于处理与服务器的通信

    private final String user;

    public Client(String user, ExecutorService exec) {
        this.user = user;
        this.exec = exec;
        try {
            ui = FXMLLoader.load(getClass().getResource("ui/client-management.fxml"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public Client(String user) {
        this(user, Executors.newSingleThreadExecutor());
    }

    public Parent getUI() {
        return ui;
    }

    public void showInNewWindow() {
        Scene scene = new Scene(ui);
        Stage stage = new Stage();
        stage.setScene(scene);
        stage.show();
    }

    public void checkForNewEmail() {
        Task<List<Email>> newEmailTask = new Task<>() {
            @Override
            protected List<Email> call() throws Exception {
                List<Email> newEmails = new ArrayList<>();
                // 与服务器通信并检索任何新电子邮件
                return newEmails;
            }
        };
        newEmailTask.setOnSucceeded(e -> {
            List<Email> newEmails = newEmailTask.getValue();
            // 使用新电子邮件更新 UI...
        });
        exec.submit(newEmailTask);
    }

    // 等等...
}

然后你的 `ClientController` 类可以这样做

```java
public class ClientController {

    private ExecutorService exec = Executors.newCachedThreadPool();

    public void startClients() {
        Client clientA = new Client("aaaa@gmail.com", exec);
        Client clientB = new Client("bbbb@gmail.com", exec);
        clientA.showInNewWindow();
        clientB.showInNewWindow();
    }
}

最后你的应用程序类可以这样

```java
public class ClientApp extends Application {

    @Override
    public void start(Stage primaryStage) {
        ClientController controller = new ClientController();
        controller.startClients();
    }

    public static void main(String[] args) {
        Application.launch(args);
    }
}
英文:

There are several problems with the structure of the application as you have posted it in the question.

The Application class represents the lifecycle of the entire application, which is managed via calls to its init(), start() and stop() methods. There should be only one Application instance in the entire application, and typically this instance is created by the JavaFX startup mechanism so you should not instantiate the Application subclass yourself.

JavaFX applications require the JavaFX runtime to be started, which includes launching the JavaFX Application Thread. This is done via a call to the static Application.launch() method, which must be called only once. The launch() method starts the JavaFX runtime, creates the instance of the Application class, calls init(), and then calls start() on the FX Application Thread. (In JavaFX 9 and later, you can also start the runtime by calling Platform.startup(), but use cases for this are rare).

Note that in your application, there is no call to Application.launch() (or Platform.startup()), so the JavaFX runtime is never started.

Certain operations can only be performed on the FX Application Thread. These include creating Stages and Scenes, and any modifications of properties of UI elements that are already displayed. Thus you cannot "run each client" in a separate thread. This is the cause of your exception: you are trying to create a new Stage on a thread that is not the FX Application Thread.

Each client does not need a new thread to display the UI (and, as described above, cannot do that). You likely do need to perform each client's communication with the server on a separate thread (because those are operations that take a long time, and you should not block the FX Application thread). You can do that by creating a new thread for each client's server communication, or using a shared executor service so that each client can get a thread from a pool (this is probably the preferred approach).

So your structure should look something like this:

public class Client {
private Parent ui ;
private ExecutorService exec ; // for handling server communication
private final String user ;
public Client(String user, ExecutorService exec) {
this.user = user ;
this.exec = exec ;
try {
ui = FXMLLoader.load(getClass().getResource(&quot;ui/client-management.fxml&quot;));
} catch (IOException e) {
e.printStackTrace();
}
}
public Client(String user) {
this(user, Executors.newSingleThreadExecutor());
}
public Parent getUI() {
return ui ;
}
public void showInNewWindow() {
Scene scene = new Scene(ui);
Stage stage = new Stage();
stage.setScene(scene);
stage.show();
}
public void checkForNewEmail() {
Task&lt;List&lt;Email&gt;&gt; newEmailTask = new Task&lt;&gt;() {
@Override
protected List&lt;Email&gt; call() throws Exception {
List&lt;Email&gt; newEmails = new ArrayList&lt;&gt;();
// contact server and retrieve any new emails
return newEmails ;
}
};
newEmailTask.setOnSucceeded(e -&gt; {
List&lt;Email&gt; newEmails = newEmailTask.getValue();
// update UI with new emails...
});
exec.submit(newEmailTask);
}
// etc ...
}

Then your ClientController class could do something like this:

public class ClientController {
private ExecutorService exec = Executors.newCachedThreadPool();
public void startClients() {
Client clientA = new Client(&quot;aaaa@gmail.com&quot;, exec);
Client clientB = new Client(&quot;bbbb@gmail.com&quot;, exec);
clientA.showInNewWindow();
clientB.showInNewWindow();
}
}

and your app class can do

public class ClientApp extends Application {
@Override
public void start(Stage primaryStage) {
ClientController controller = new ClientController();
controller.startClients();
}
public static void main(String[] args) {
Application.launch(args);
}
}

huangapple
  • 本文由 发表于 2020年8月24日 21:37:11
  • 转载请务必保留本文链接:https://go.coder-hub.com/63562168.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定