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

时间:2019-08-09 21:36:53

标签: java javafx

我在JavaFX应用程序上工作时,发现运行了几次后,ScheduledService停止了调度。

我找不到任何明显的原因。当我跟踪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 );
    }
}

输出

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

这是一个错误吗?

我正在使用

Windows 10

OracleOpen_jdk-12.0.1

javafx-sdk-12.0.1

1 个答案:

答案 0 :(得分:3)

我可以在以下环境中重现该问题:

  • Windows 10
  • OpenJDK 12.0.1
  • OpenJFX 12.0.1

稍稍修改代码即可了解问题所在:

import java.lang.ref.Cleaner;
import java.util.concurrent.atomic.AtomicInteger;
import javafx.application.Application;
import javafx.concurrent.ScheduledService;
import javafx.concurrent.Task;
import javafx.stage.Stage;

public class Main extends Application {

    private static final Cleaner CLEANER = Cleaner.create();
    private static final AtomicInteger COUNTER = new AtomicInteger();

    @Override
    public void start(Stage primaryStage) {
        var service = new ScheduledService<Void>() {

            @Override
            protected Task<Void> createTask() {
                return new MyTask();
            }

        };
        CLEANER.register(service, () -> {
            System.out.println("SERVICE GARBAGE COLLECTED!");
            System.exit(0);
        });
        service.start();
    }

    private static class MyTask extends Task<Void> {

        @Override
        protected Void call() throws Exception{
            System.out.println("MyTask#call() " + COUNTER.incrementAndGet());
            return null;
        }

    }

}

以上输出:

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
MyTask#call() 15
MyTask#call() 16
MyTask#call() 17
MyTask#call() 18
MyTask#call() 19
SERVICE GARBAGE COLLECTED!

因此可以解释发生了什么事-ScheduledService实例正在被垃圾回收。这是有道理的,因为您无需维护对ScheduledService实例的强引用,也不会执行Task实例。一旦ScheduledService实例被垃圾回收,就无法为下一个执行周期安排另一个Task


  

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

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


return new MyTask();// if i use new Task<Void>{...} it works fine

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

ScheduledService类使用内部java.util.Timer实例调度Task以便以后执行。因此,在等待执行期间,Task可以通过Timer使用的线程来访问。在执行期间,Task可以由执行它的线程强烈访问(该线程来自Executor属性中使用的ScheduledService#executor)。

因此,当使用由匿名Task括起来的匿名ScheduledService时,ScheduledService最终是很容易到达的。

  • 计划/执行线程→任务→ScheduledService

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