为什么javaFX的ScheduledService不能与具体类Task一起使用?

Sai*_*ila 7 java javafx

当我注意到几次运行后停止调度ScheduledService时,我正在使用JavaFX应用程序。

我找不到任何明显的原因。当我跟踪ScheduledService的状态时,似乎它切换到了SCHEDULED状态,然后变得无声。我将代码减少到几乎没有,以期缩小问题范围。我发现,当我ScheduledService创建anonymos类的Task时,不会发生此问题,但是当我使用子类或顶级类时,会发生此问题。

package application;

import javafx.application.Application;
import javafx.concurrent.ScheduledService;
import javafx.concurrent.Task;
import javafx.stage.Stage;

public class MyApplication extends Application
{
    private static volatile int counter;

    @Override
    public void start( Stage primaryStage )
    {
        ScheduledService<Void> svc = new ScheduledService<>()
        {
            @Override
            protected Task<Void> createTask()
            {
                return new MyTask();// if i use new Task<Void>{...} it works fine
            }
        };
        svc.start();
    }

    private static class MyTask extends Task<Void>
    {
        @Override
        protected Void call() throws Exception
        {
            System.out.println( "MyTask#call() " + counter++ );
            return null;
        }
    }

    public static void main( String[] args )
    {
        launch( args );
    }
}
Run Code Online (Sandbox Code Playgroud)

输出

MyTask#call() 0
MyTask#call() 1
MyTask#call() 2
MyTask#call() 3
MyTask#call() 4
MyTask#call() 5
MyTask#call() 6
MyTask#call() 7
MyTask#call() 8
MyTask#call() 9
MyTask#call() 10
MyTask#call() 11
MyTask#call() 12
MyTask#call() 13
MyTask#call() 14
Run Code Online (Sandbox Code Playgroud)

这是错误吗?

我正在使用

Windows 10

OracleOpen_jdk-12.0.1

javafx-sdk-12.0.1

Sla*_*law 3

我可以使用以下环境重现该问题:

\n\n
    \n
  • Windows 10
  • \n
  • OpenJDK 12.0.1
  • \n
  • OpenJFX 12.0.1
  • \n
\n\n

稍微修改一下代码就可以看出问题所在:

\n\n
import java.lang.ref.Cleaner;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport javafx.application.Application;\nimport javafx.concurrent.ScheduledService;\nimport javafx.concurrent.Task;\nimport javafx.stage.Stage;\n\npublic class Main extends Application {\n\n    private static final Cleaner CLEANER = Cleaner.create();\n    private static final AtomicInteger COUNTER = new AtomicInteger();\n\n    @Override\n    public void start(Stage primaryStage) {\n        var service = new ScheduledService<Void>() {\n\n            @Override\n            protected Task<Void> createTask() {\n                return new MyTask();\n            }\n\n        };\n        CLEANER.register(service, () -> {\n            System.out.println("SERVICE GARBAGE COLLECTED!");\n            System.exit(0);\n        });\n        service.start();\n    }\n\n    private static class MyTask extends Task<Void> {\n\n        @Override\n        protected Void call() throws Exception{\n            System.out.println("MyTask#call() " + COUNTER.incrementAndGet());\n            return null;\n        }\n\n    }\n\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

以上输出:

\n\n
import java.lang.ref.Cleaner;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport javafx.application.Application;\nimport javafx.concurrent.ScheduledService;\nimport javafx.concurrent.Task;\nimport javafx.stage.Stage;\n\npublic class Main extends Application {\n\n    private static final Cleaner CLEANER = Cleaner.create();\n    private static final AtomicInteger COUNTER = new AtomicInteger();\n\n    @Override\n    public void start(Stage primaryStage) {\n        var service = new ScheduledService<Void>() {\n\n            @Override\n            protected Task<Void> createTask() {\n                return new MyTask();\n            }\n\n        };\n        CLEANER.register(service, () -> {\n            System.out.println("SERVICE GARBAGE COLLECTED!");\n            System.exit(0);\n        });\n        service.start();\n    }\n\n    private static class MyTask extends Task<Void> {\n\n        @Override\n        protected Void call() throws Exception{\n            System.out.println("MyTask#call() " + COUNTER.incrementAndGet());\n            return null;\n        }\n\n    }\n\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

这就解释了发生了什么\xe2\x80\x94ScheduledService实例正在被垃圾收集。这是有道理的,因为您不维护对ScheduledService实例的强引用,并且正在执行的实例也不维护Task。一旦ScheduledService实例被垃圾回收,它就无法Task为下一个执行周期安排另一个实例。

\n\n
\n\n
\n

我仍然有点困惑为什么它与匿名类一起使用。我的意思是在我的示例中,类 MyTask 是静态的,但即使它不是静态的,它也不起作用。非静态内部类的对象应该具有对外部类对象的引用。

\n
\n\n

确实,非静态嵌套或匿名类维护对封闭类实例的引用。但是,封闭类是MainMyApplication在您的代码中),而不是匿名ScheduledService类。

\n\n
\n\n
\n
return new MyTask();// if i use new Task<Void>{...} it works fine\n
Run Code Online (Sandbox Code Playgroud)\n
\n\n

使用有效的原因new Task<Void>() { ... }是因为现在封闭的实例是ScheduledService实例。

\n\n

该类ScheduledService使用内部java.util.Timer实例来调度Task稍后执行。因此,在等待执行时,Task所使用的线程可以强烈访问Timer. 在执行时,Task执行它的线程可以强访问它(该线程来自Executoruse with inScheduledService#executor属性)。

\n\n

Task因此,当使用被匿名包围的匿名时ScheduledServiceScheduledService最终是强可达的。

\n\n
    \n
  • 调度/执行线程 \xe2\x86\x92 任务 \xe2\x86\x92 ScheduledService
  • \n
\n\n
\n\n

奇怪的是,如果我添加代码来显示主对象Stage,则ScheduledService永远不会被垃圾收集。我不确定为什么会这样。即使我使用Platform.setImplicitExit(false)并关闭Stage.

\n