3

I have a method TaskManager.newRepeatingTask(Runnable r, long delay, long interval) and it returns a UUID. I assign a UUID variable to what that method returned, and I want to use that variable inside of the Runnable. How would I accomplish this effectively, or alternatively, to what I'm 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);
2
  • What package does TaskManager belong to? Commented Sep 14, 2014 at 1:40
  • TaskManager is in my utils, and I have complete control of it. What this information was needed for I have no idea. The problem is officially solved now. Commented Sep 14, 2014 at 13:23

4 Answers 4

5

Use a java.util.concurrent.CompletableFuture (new in Java 8) to transmit a value between threads or tasks when you're not sure which one will arrive 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 join() method will collect and return the value supplied by the complete() method. If complete() hasn't been called yet, join() will block until it is. CompletableFuture handles all synchronization and memory visibility issues internally.

As others have noted, this is a bit contrived. A more conventional approach for a repeating task to cancel itself is to have it return a boolean indicating whether it should be rescheduled or canceled. To do this, change TaskManager.newRepeatingTask() to take a Supplier<Boolean> instead of a Runnable.

Sign up to request clarification or add additional context in comments.

Comments

4

You could have your TaskManager.newRepeatingTask accept a Consumer<UUID>. Then create a runnable from that, using your then known UUID.

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 could just do:

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

10 Comments

@CoderMusgrove In the declaration of the method newRepeatingTask, declare a Consumer<UUID> as first parameter instead of Runnable. You'll then be able to provide a lambda that takes a UUID as parameter, as shown in the second snippet of code here.
@CoderMusgrove About lambdas: in short, (UUID id) -> {...} is a lambda that implements Consumer<UUID>, while () -> {...} is a lambda that implements Runnable. That's why you can use these lambdas when calling methods that respectively expect a Consumer<UUID> parameter, or a Runnable parameter.
I'm a C# guy so I actually didn't know java had lambdas before just now. I've found that using lambdas as you progress and understand them is a much better way to learn about them than to read some tutorial. It's just a way to make your functionality become first class members as data in your program. That is, you can pass functionality around as you would ints, strings etc. This lends itself to enable a functional style of programming. If you want to learn more about lambdas, this article was not all bad: docs.oracle.com/javase/tutorial/java/javaOO/… - Good luck!
@AlexanderBrevig lambdas in Java are quite recent: they came with Java 8 in March 2014. That's probably why you didn't know about it ^^
Ah I see! I used Java for some internal tools at my company using 7th ed. I knew lamdas were coming, but never realized they were available already. I <3 λ
|
3

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

  • create a Runnable (anonym) subclass with a settable (e.g. public) field uuid
  • call newRepeatingTask with your runnable object and get the UUID
  • use the setter to set the UUID on the Runnable

That would 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're gonna have to drop the lambda :'(

Important note: as noted by @Dici, if the runnable is run within newRepeatingTask, some synchronization problems might happen. You might consider the option suggested by AlexanderBrevig, which would allow you to set the id before calling run() on the runnable.

4 Comments

or have newRepeatingTask only accept the new subclass, and have it automatically set the UUID
yeah that would be a solution too, but a bit too restrictive maybe
@Joffrey : What if TaskManager.cancelTask(id); is called before r.uuid = id; ? Doesn't it need a bit of synchronization ?
It would need it indeed, unless run() is not called within newRepeatingTask
2

My first solution, if I had control over TaskManager, would be to change it such that it also passed a UUID parameter to the callback or had another method of control - then using the result of the method would be moot.

However, if I did not then..


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

Another approach is to use a "mutable reference wrapper", like 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);

Like the Runnable-with-UUID approach, this also suffers from a potential race condition between the assignment of the ID and the potential usage inside the lambda/Runnable if the task is run on a different thread. (If run later on the same thread then synchronization issues need not apply; and if run immediately on the same thread then the UUID is never observable inside the lambda.)

Applying a shared-synchronization both outside/wrapping the method call itself (and inside around the applicable code) should take care of that unless the Runnable is called immediately. Synchronization, or equivalent, should be done anyway for guaranteed visibility reasons (even if there is no "race condition") if such an approach is taken and the task may be executed 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 Comments

+1 for the synchronization when the code of newRepeatingTask() is not controlled.
Close, +1. In Java 8, Holder is spelled CompletableFuture and handles synchronization and memory visibility.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.