How do I use a variable in a lambda when the variable has been assigned as a result of calling the method that the lambda was sent to?

I have a method TaskManager.newRepeatingTask(Runnable r, long delay, long interval)

and it returns UUID

. I am assigning a variable UUID

to what this method returned and I want to use that variable internally Runnable

. How can I accomplish this efficiently, or alternatively to what I am trying to accomplish here?

UUID id = TaskManager.newRepeatingTask(() -> {
    for (int i = 0; i < profileButtons.length; i++) {
        GuiButton button = profileButtons[i];
        button.move(-1, 0);
    }
    toMove--;
    if (toMove == 0) {
        // id: "The variable may not have been initialized"
        TaskManager.cancelTask(id);
    }
}, 30L, 0L);

      

+3


source to share


4 answers


Use java.util.concurrent.CompletableFuture

(new in Java 8) to pass value between threads or tasks when you're not sure which one comes first. Here's how:

CompletableFuture<UUID> id = new CompletableFuture<>();
id.complete(TaskManager.newRepeatingTask(() -> {
    for (int i = 0; i < profileButtons.length; i++) {
        GuiButton button = profileButtons[i];
        button.move(-1, 0);
    }
    toMove--;
    if (toMove == 0) {
        TaskManager.cancelTask(id.join());
    }
}, 30L, 0L));

      



The method join()

will collect and return the value provided by the method complete()

. If complete()

not called yet, join()

will block until it is called. CompletableFuture

handles all sync and memory visibility issues internally.

As others have pointed out, this is a bit far-fetched. A more traditional approach to re-tasking itself for cancellation is to have it return a boolean indicating whether it should be rescheduled or canceled. To do this, change TaskManager.newRepeatingTask()

to Runnable

take instead Supplier<Boolean>

.

+5


source


You might have a TaskManager.newRepeatingTask

Consumer <UUID>. Then create an executable from this using the already known one UUID

.

So so that you internally go something like this:

//inside newRepeatingTask(consumer:Consumer<UUID> ...)
Runnable r = new Runnable() {
    public UUID uuid;
    @Override
    public void run() {
        consumer.accept(uuid); //calls the lambda
    }
};
r.uuid = getNextUUID(); //do whatever here
//add to your presumed list of runnables

      



Now you can simply do:

UUID id = TaskManager.newRepeatingTask((UUID id) -> {
    TaskManager.cancelTask(id);
    //probably do something better with id
}, 30L, 0L);
//LOOK MA, this code is DRY

      

+4


source


I think you'll have to complicate things a little. The first thing that comes to my mind is the following:

  • subclass Runnable

    (anonymous) with a custom (e.g. public) fielduuid

  • call newRepeatingTask

    with your executable and get the UUID
  • use setter to set UUID to Runnable

It will be:

Runnable r = new Runnable() {
    public UUID uuid;

    @Override
    public void run() {
        for (int i = 0; i < profileButtons.length; i++) {
            GuiButton button = profileButtons[i];
            button.move(-1, 0);
        }
        toMove--;
        if (toMove == 0) {
            // id: "The variable may not have been initialized"
            TaskManager.cancelTask(uuid);
        }
    }
}
UUID id = TaskManager.newRepeatingTask(r, 30L, 0L);
r.uuid = id;

      

Sorry, but I think you will have to reset the lambda: '(

Important note: as @Dici pointed out, if the runnable is executed in newRepeatingTask

, there may be some timing issues. You may want to consider the option suggested by AlexanderBrevig, which will allow you to set the id before the call run()

to runnable.

+3


source


My first solution, if I had control over TaskManager

, would be to change it so that it also pass the UUID parameter to the callback, or have a different control method - then using the result of the method would be moot.

However, if I didn't, then ..


(Edit: I was informed that the correct way to handle this in Java 8 is with CompletableFuture - see Stuart's answer.)

Another approach is to use a "mutable reference wrapper" for example Holder<T>

(or T[]

or make your own) to emulate mutable bindings. Then,

Holder<UUID> idRef = new Holder<UUID>(); // Effectively final

idRef.value = TaskManager.newRepeatingTask(() -> {
    for (int i = 0; i < profileButtons.length; i++) {
        GuiButton button = profileButtons[i];
        button.move(-1, 0);
    }
    toMove--;
    if (toMove == 0) {
        UUID id = idRef.value;
        TaskManager.cancelTask(id);
    }
}, 30L, 0L);

      

Similar to the Runnable-with-UUID approach, this also suffers from a potential race condition between identifier assignment and potential use inside a lambda / Runnable if the task is executed on a different thread. (If you run later on the same thread, then synchronization issues should not apply, and if they run immediately on the same thread, then the UUID will never be seen inside the lambda.)

When applying co-synchronization both outside / wrapping the method invocation itself (and inside around the applicable code), you should take care of this unless Runnable is called immediately. Synchronization or equivalent must be done anyway for guaranteed apparent reasons (even if there is no "race condition") if that approach is accepted and the task can be performed on a different thread.

Holder<UUID> idRef = new Holder<UUID>();

synchronized(idRef) {
  idRef.value = TaskManager.newRepeatingTask(() -> {
      for (int i = 0; i < profileButtons.length; i++) {
          GuiButton button = profileButtons[i];
          button.move(-1, 0);
      }
      toMove--;
      if (toMove == 0) {
          // id: "The variable may not have been initialized"
          synchronized(idRef) {
            UUID id = idRef.value;
            TaskManager.cancelTask(id);
          }
      }
  }, 30L, 0L);
}

      

+2


source







All Articles