Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does the ScheduledService from javaFX not work with a concrete class as Task?

Tags:

java

javafx

I was working on my JavaFX application when i noticed that on ScheduledService stopped being scheduled after a couple of runs.

I was not able to find any obvious reason for this. When i tracked that state of the ScheduledService it seemed that it switched to the the state SCHEDULED and then went silent. I reduced my code to almost nothing in the hope to narrow down the issue. I discovered that when i ScheduledService creates a Task of an anonymos class, the issue does not occur but when i use sub class or top level class it does.

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 );
    }
}

The output

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

Is this a bug?

I'm Using

Windows 10

OracleOpen_jdk-12.0.1

javafx-sdk-12.0.1

like image 716
Dieter Avatar asked Aug 09 '19 21:08

Dieter


1 Answers

I can reproduce the problem using the following environment:

  • Windows 10
  • OpenJDK 12.0.1
  • OpenJFX 12.0.1

Modifying the code slightly shows you what the problem is:

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;
        }

    }

}

The above outputs:

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!

So that explains what is happening—the ScheduledService instance is being garbage collected. This makes sense since you don't maintain a strong reference to the ScheduledService instance and neither does the Task instance being executed. Once the ScheduledService instance is garbage collected it can't schedule another Task for the next execution cycle.


I'm still a bit confused why it works with a anonymous class then. I mean in my example the class MyTask is static but even when it is not it does not work. Objects of a non-static inner class should have a reference to there outer class objects.

It's true that a non-static nested or anonymous class maintains a reference to an instance of the enclosing class. However, the enclosing class is Main (MyApplication in your code), not the anonymous ScheduledService class.


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

The reason using new Task<Void>() { ... } works is because now the enclosing instance is the ScheduledService instance.

The ScheduledService class uses an internal java.util.Timer instance for scheduling the Task for later execution. So while waiting to be executed the Task is strongly reachable by the thread used by the Timer. While being executed, the Task is strongly reachable by the thread executing it (the thread comes from the Executor used with in ScheduledService#executor property).

So, when using an anonymous Task enclosed by the anonymous ScheduledService, the ScheduledService is ultimately strongly reachable.

  • Scheduling/Executing Thread → Task → ScheduledService

Curiously, if I add code to display the primary Stage the ScheduledService is never garbage collected. I'm not sure why this is the case. This is true even when I use Platform.setImplicitExit(false) and close the Stage.

like image 85
Slaw Avatar answered Oct 07 '22 13:10

Slaw