【问题标题】:How to ensure ScheduledService stops when last window closes in JavaFXJavaFX 中最后一个窗口关闭时如何确保 ScheduledService 停止
【发布时间】:2025-11-22 21:50:03
【问题描述】:

我正在使用 ScheduledService 来确保服务器连接在应用程序打开时保持活动状态,并且用户不会主动与应用程序交互。

有时应用程序关闭时应用程序线程没有结束,我假设可能是 ScheduledService 导致了我的问题。

由于我的应用程序将包含多个同时打开的阶段,因此我无法在窗口关闭时停止服务。有没有其他方法可以在线程执行过程中查看某个stage当前是否打开,如果没有则取消服务?

//Service set up
service = new KeepSessionAliveService() ;
service.setPeriod(Duration.minutes(REPEAT_PERIOD));
service.setDelay(Duration.minutes(INITIAL_DELAY));
service.start();

private class KeepSessionAliveService extends ScheduledService<Void> {
    @Override
    protected Task<Void> createTask() {
        return new Task<Void>() {
            @Override
            protected Void call() throws ClientProtocolException, IOException {
                pingServerToKeepAlive();
                return null;
            }
        };
    }
}

如果这不是最好的处理方式,我愿意接受建议。

谢谢。

编辑: 我试图提供服务器访问的默认实现,开发人员只需调用一个包装器方法来提供所需的预期类定义。当调用被调用时,它将在进程运行时提供进度警报,然后在完成时关闭它。最初我尝试了一项服务,但似乎无法使其正常工作,因此建议尝试以下方法:

public <T> void executeServerCall(ServiceRequest serviceRequest, String message, Stage stage, Class<T> valueType, Consumer<T> success, Consumer<Exception> failure) {
    Alert progressAlert = displayProgressDialog(message, stage);
    Executors.newSingleThreadExecutor().execute(() -> {
           try {
               T result = executeServerCall(serviceRequest, valueType);
               Platform.runLater(() ->
                {
                    forcefullyHideDialog(progressAlert);
                    success.accept(result);
                });
            } catch (Exception e) {
                Platform.runLater(() ->
                {
                    forcefullyHideDialog(progressAlert);
                    failure.accept(e);
                });
            }

    });
}

根据您的回复,我确实证明是这段代码让应用程序线程运行,而不是服务。如果我现在可以通过服务完成我所需要的,我会非常乐意改变它。但是我找不到执行以下操作的方法:

public <T> void executeServerCall(ServiceRequest serviceRequest, String message, Stage stage, Class<T> valueType, Consumer<T> success, Consumer<Exception> failure) {

    Service<valueType> service = ....

【问题讨论】:

  • 默认情况下,ScheduledService(和Service)将创建一个守护线程来执行创建的任务。 (见Javadocs for Service)。因此,除非您为服务指定执行者(在这种情况下显示该代码),否则服务不应该是这里的罪魁祸首。
  • 这就是我的假设。但是,当我从 Eclipse 运行时,此服务会在窗口关闭时停止,但是在 EXE 上,FX 线程继续运行。我没有在这个线程上指定一个执行者,但这是我唯一安排的事情。我还使用以下内容: Executors.newSingleThreadExecutor().execute(() -> {});与我的服务器交互。但是,在我关闭应用程序之前,这个线程早就停止了,所以我不认为它可能是那个?我不明白为什么应用程序线程不会停止。
  • Executors.newSingleThreadExecutor() 创建一个执行程序,它使用单个线程执行您传递的任何Runnable,但线程将在(每个)Runnable 完成后持续存在,除非Runnable 由于以下原因而失败一个例外。 (请参阅docs。)从您的代码 sn-p 看来,您正在创建许多这样的执行程序,这是一个非常糟糕的主意,因为最终您的应用程序将遇到线程饥饿。在问题中编辑您正在做什么的详细信息,或创建一个minimal reproducible example

标签: concurrency javafx-8


【解决方案1】:

您正在创建许多单线程执行器,在这些执行器上执行对服务器的每个单独调用。这些执行程序中的每一个都创建自己的非守护线程,然后等待下一个可运行的执行。

如果所有这些对服务器的调用都应该发生在单个线程上,那么您应该创建一个执行器来执行此操作。 (确实,如果您希望它们发生在多个线程上,您仍然应该创建一个执行器,只是一个使用多个线程而不是单个线程的执行器。)

所以:

private final ExcecutorService serverCallExec = Executors.newSingleThreadedExecutor();

// ...

public <T> void executeServerCall(ServiceRequest serviceRequest, String message, Stage stage, Class<T> valueType, Consumer<T> success, Consumer<Exception> failure) {
    Alert progressAlert = displayProgressDialog(message, stage);
    serverCallExec.execute(() -> {
           try {
               T result = executeServerCall(serviceRequest, valueType);
               Platform.runLater(() ->
                {
                    forcefullyHideDialog(progressAlert);
                    success.accept(result);
                });
            } catch (Exception e) {
                Platform.runLater(() ->
                {
                    forcefullyHideDialog(progressAlert);
                    failure.accept(e);
                });
            }

    });
}

要让执行程序在应用程序关闭时终止其线程,可以安排从您的Application.stop() 方法调用serverCallExec.shutdown();,或者通过指定线程工厂使执行程序使用的线程成为守护线程:

private final ExecutorService serverCallExec = Executors.newSingleThreadedExecutor((Runnable r) -> {
    Thread t = new Thread(r);
    t.setDaemon(true);
    return t ;
});

【讨论】:

    【解决方案2】:

    感谢您的建议。
    当我们来回走动时,我尝试了这个。

    public <T> void executeServerCall(ServiceRequest serviceRequest, String message, Stage stage, Class<T> valueType, Consumer<T> success, Consumer<Throwable> failure) {
        Service<T> service = new Service<T>(){
            @Override
            protected Task<T> createTask() {
                return new Task<T>() {
                    @Override
                    protected T call() throws Exception {
                        return executeServerCall(serviceRequest, valueType);
                    }
                };
            }
        };
        service.setOnSucceeded(event -> success.accept(service.getValue()));
        service.setOnFailed(event -> failure.accept(service.getException()));
    
        ProgressDialog pd = new ProgressDialog(service);
        pd.setContentText(message);
        pd.initModality(Modality.WINDOW_MODAL);
        pd.initOwner(stage);
    
        service.start();
    }
    

    这解决了我的问题并使用并发服务来处理我的问题。我不知道为什么这以前不起作用(我可能在创建服务时进行了一些奇怪的设置)。

    与您在上面发布的满足我的需求的解决方案相比,这是一个更好的解决方案吗?我只是想抽象出呼叫的管道。这似乎可以做到。

    【讨论】:

    • 事实上,当 exe 的最后一个窗口关闭时,线程就消失了。非常感谢您解释我做错了什么!
    • 如果它符合您的要求,那肯定会更干净。它将正常终止,因为默认情况下为 ScheduledService 创建的线程是守护线程。
    • 它就像一个魅力。再次感谢,非常感谢您对这些论坛的帮助和洞察力。这就是我最初尝试这样做的方式,我认为这是我试图做的:Service,因为它是传入的,而不是 Service。只是愚蠢的错误。为我提供了 Executor 解决方案,并且在它起作用后没有三思而后行。但这更干净,而且我一开始就想这样做。
    最近更新 更多