Reputation: 614
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);
Upvotes: 3
Views: 312
Reputation: 61885
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);
}
Upvotes: 2
Reputation: 132390
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
.
Upvotes: 5
Reputation: 1987
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
Upvotes: 4
Reputation: 37720
I think you'll have to complicate a bit here. The first thing that comes to my mind is the following:
Runnable
(anonym) subclass with a settable (e.g. public) field uuid
newRepeatingTask
with your runnable object and get the UUIDThat 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.
Upvotes: 3