Wolfgang Fahl
Wolfgang Fahl

Reputation: 15769

Java FX Platform.runLater(() -> equivalent for long running Tasks

I learned that in JavaFX the equivalent of

SwingUtilities.invokeLater(new Runnable() {
  public void run() {
   dosomething();
 }
});

might simply be

Platform.runLater(() ->{ dosomething()};

for a long running task I learned that you need to wrap things with a Task like:

Task<Void> task = new Task<Void>() {
  @Override
  public Void call() {
    dosomething();
  }
};
new Thread(task).start();

Now it would be great to be able to have a similar lambda shortcut like

TaskLaunch.start(() -> dosomething());

I found

discussing some of the issues around this and tried:

package com.bitplan.task.util;    
import java.util.concurrent.Callable;
import javafx.concurrent.Task;

/**
 * this is a utility task to launch tasks with lambda expressions
 * 
 * @author wf
 *
 */
public class TaskLaunch {
  /**
   * 
   * @param callable
   * @return the new task
   */
  public static <T> Task<T> task(Callable<T> callable) {
    Task<T> task = new Task<T>() {
      @Override
      public T call() throws Exception {
        return callable.call();
      }
    };
    return task;
  }
}

with a JUnit test:

  Integer counter=0;
  boolean running=false;
  public Integer increment() {
    running=true;
    while (running) {
      counter++;
      try {
        Thread.sleep(1);
      } catch (InterruptedException e) {
      }
    }
    return counter;
  }
  /**
   * @throws Exception 
   */
  @Test
  public void testTaskLaunch() throws Exception {
    // https://stackoverflow.com/questions/30089593/java-fx-lambda-for-task-interface
    Task<Integer> task=TaskLaunch.task(() -> increment());
    try {
      Thread.sleep(20);
    } catch (InterruptedException e) {
      //
    }
    running=false;
    assertTrue(task.get()>10);
  }

Which doesn't quite do what I'd like to see yet. The issue seems to be that the lambda expression runs in the same Thread and the

new Thread(task).start();

part needs to be integrated.

What is needed to get (at least close to) the short one liner mentioned above?

Is a

TaskLaunch.start(() -> dosomething());

feasible?

based on @Damianos proposal https://stackoverflow.com/a/44817217/1497139

I tried:

package com.bitplan.task;

import java.util.concurrent.Callable;

import javafx.concurrent.Task;

/**
 * this is a utility task to launch tasks with lambda expressions
 * 
 * @author wf
 *
 */
public class TaskLaunch<T> {

  Thread thread;
  Task<T> task;
  Callable<T> callable;
  Throwable throwable;
  Class<T> clazz;

  public Thread getThread() {
    return thread;
  }

  public void setThread(Thread thread) {
    this.thread = thread;
  }

  public Task<T> getTask() {
    return task;
  }

  public void setTask(Task<T> task) {
    this.task = task;
  }

  public Callable<T> getCallable() {
    return callable;
  }

  public void setCallable(Callable<T> callable) {
    this.callable = callable;
  }

  public Throwable getThrowable() {
    return throwable;
  }

  public void setThrowable(Throwable throwable) {
    this.throwable = throwable;
  }

  public Class<T> getClazz() {
    return clazz;
  }

  public void setClazz(Class<T> clazz) {
    this.clazz = clazz;
  }

  /**
   * construct me from a callable
   * 
   * @param callable
   */
  public TaskLaunch(Callable<T> callable, Class<T> clazz) {
    this.callable = callable;
    this.task = task(callable);
    this.clazz = clazz;
  }

  /**
   * 
   * @param callable
   * @return the new task
   */
  public static <T> Task<T> task(Callable<T> callable) {
    Task<T> task = new Task<T>() {
      @Override
      public T call() throws Exception {
        return callable.call();
      }
    };
    return task;
  }

  /**
   * start
   */
  public void start() {
    thread = new Thread(task);
    thread.start();
  }

  /**
   * start the given callable
   * @param callable
   * @param clazz - the return Type class
   * @return - the launch result
   */
  @SuppressWarnings({ "unchecked", "rawtypes" })
  public static TaskLaunch start(Callable<?> callable, Class<?> clazz) {
    TaskLaunch<?> launch = new TaskLaunch(callable, clazz);
    launch.start();
    return launch;
  }

}

and changed the test to:

  /**
   * @throws Exception 
   */
  @SuppressWarnings("unchecked")
  @Test
  public void testTaskLaunch() throws Exception {
    // https://stackoverflow.com/questions/30089593/java-fx-lambda-for-task-interface
    TaskLaunch<Integer> launch = TaskLaunch.start(()->increment(),Integer.class);
    try {
      Thread.sleep(20);
    } catch (InterruptedException e) {
      //
    }
    running=false;
    assertTrue(launch.getTask().get()>10);
  }

This is close to what i am up to but I get:

java.lang.IllegalStateException: Toolkit not initialized
at com.sun.javafx.application.PlatformImpl.runLater(PlatformImpl.java:273)
at com.sun.javafx.application.PlatformImpl.runLater(PlatformImpl.java:268)
at javafx.application.Platform.runLater(Platform.java:83)
at javafx.concurrent.Task.runLater(Task.java:1225)
at javafx.concurrent.Task$TaskCallable.call(Task.java:1417)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.lang.Thread.run(Thread.java:745)

At least TaskLaunch now wraps:

  1. Thread
  2. task
  3. callable
  4. a potential Exception/Throwable
  5. the runtime class of the result of the Task

Some of these 5 items might be redundant and available from the standard java concepts. I think at least its handy to have quick access to these after running things from a one liner.

Hope this gets to a working state and thanks for the help!

Upvotes: 0

Views: 4590

Answers (4)

Wolfgang Fahl
Wolfgang Fahl

Reputation: 15769

The answer is now in the question based on Damianos hint.

The workaround for the exception I found is

com.sun.javafx.application.PlatformImpl.startup(() -> {
});

But seems a little bit hacky ...

Upvotes: -1

Jai
Jai

Reputation: 8363

Doing this will cause you to lose the ability to update stuff back to the UI thread natively supported by Task class. On the other hand, I do agree this can be useful if you want to do something in background in "do-and-forget" style.

The problem is just like what you said - you didn't add new Thead() and Thread.start() in. Do this:

public static void runInBackground(Runnable runnable) {
    Task<Void> task = new Task<>() {
        @Override
        public Void call() throws Exception {
            runnable.run();
            return null;
        }
    };
    new Thead(task).start();
}

runInBackground(() -> System.out.println(Thread.currentThread().hashCode()));

Note that your Task can no longer be non-void, because it cannot return anything back now. Your lambda needs to be able to reference the Task object to return a result asynchronously - that is never going to be possible using lambda.

Upvotes: 1

Mordechai
Mordechai

Reputation: 16264

This is sort of a traditional XY problem.

A Task is much more than just a background thread, hence for this you can use regular threads. It's the beauty of the properties!

The real benefit of using Task is that all state changes and progress updates can safely be observed and bound to a live scene, while doing all the background work on a different thread. It's the work of the class to do the heavy-lifting and call Platform.runLater.

The reason you need a subclass and not a runnable is so you can call its protected updateXxx() methods without worrying for threading issues.

With this said, you'll have no benefit if this would've been a single line code. For this use simple threads.

Hope this helps.

Upvotes: 1

Damiano
Damiano

Reputation: 811

Just new Thread(() -> dosomething()).start() should do the trick

Upvotes: 2

Related Questions