Lightweight and Controllable Delayed Tasks
Introduction
As we know, TaskKit
provides a utility module combining tasks, timing, delay listeners, timeout listeners, and more. You can run delayed tasks with runOnce, for example:
TaskKit.runOnce(() -> log.info("2 Seconds"), 2, TimeUnit.SECONDS);
TaskKit.runOnce(() -> log.info("1 Minute"), 1, TimeUnit.MINUTES)
But in some cases, we need controllable delayed tasks, where delay time can change based on later business logic, for example: increasing delay, reducing delay, cancelling tasks, and other controllable operations.
Lightweight controllable delayed task - features
- Single responsibility principle.
- Task executes when the specified time is reached.
- Task can be cancelled.
- Task can be overwritten.
- Delay time can be increased.
- Delay time can be reduced.
- Task listener callback can be configured.
- Internally uses Netty
HashedWheelTimer, easily supporting millions of tasks.
Usage Scenarios
- Scenario 1: when a player uses an item, cooldown is reduced; or some events increase cooldown. During delayed task execution, temporary buffs can be applied to players, such as temporary double attack.
- Scenario 2: before the game starts, after room creation, auto-dissolve room after 1 minute (execute task at a future time). Before dissolving, whenever a player joins, postpone dissolve by 30 seconds (increase delay). When room size is greater than 5, cancel the auto-dissolve rule (cancel task).
These two scenarios mainly describe that delayed tasks need to support increasing/reducing delay time and support cancellation.
Usage Example
Demo - Delayed Task
The sample task executes after 1 second.
- code 6: increase delay by 1 second.
- code 7: start the task.
public void runDelayTask() {
DelayTaskKit.of(() -> {
// your code
log.info("1 seconds");
})
.plusTime(Duration.ofSeconds(1))
.task();
}
Demo - Increase Delay Time
This sample demonstrates increasing delay time. The delayed task finally executes after 1.5 seconds.
- code 7: add 1 second delay.
- code 10: add 0.5 second delay.
public void plusDelayTime() {
long timeMillis = System.currentTimeMillis();
DelayTask delayTask = DelayTaskKit.of(() -> {
long value = System.currentTimeMillis() - timeMillis;
log.info("{}", value);
})
.plusTime(Duration.ofSeconds(1))
.task();
delayTask.plusTimeMillis(500);
}
Demo - Decrease Delay Time
This sample demonstrates reducing delay time. The delayed task finally executes after 0.5 seconds.
- code 8: add 1 second delay.
- code 11: reduce delay by 0.5 second.
public void minusDelayTime() {
long timeMillis = System.currentTimeMillis();
DelayTask delayTask = DelayTaskKit.of(() -> {
long value = System.currentTimeMillis() - timeMillis;
log.info("{}", value);
})
.plusTime(Duration.ofSeconds(1))
.task();
delayTask.minusTimeMillis(500);
}
Demo - Overwrite Delayed Task
This sample demonstrates overwriting delayed tasks.
- code 5: start task.
- code 11: because
taskIdis the same, it overwrites the previous delayed task, and the overwritten task is removed.
public void coverDelayTask() throws InterruptedException {
String taskId = "1";
DelayTaskKit.of(taskId, () -> log.info("task - 1"))
.plusTime(Duration.ofSeconds(2))
.task();
TimeUnit.MILLISECONDS.sleep(500);
long timeMillis = System.currentTimeMillis();
DelayTask delayTask = DelayTaskKit.of(taskId, () -> {
long value = System.currentTimeMillis() - timeMillis;
log.info("{}", value);
})
.plusTime(Duration.ofSeconds(1))
.task();
}
Demo - Cancel Delayed Task
This sample demonstrates cancelling delayed tasks.
- code 11: cancel task through
DelayTask. - code 22: cancel task through
taskId.
public void cancelDelayTask() throws InterruptedException {
DelayTask delayTask = DelayTaskKit.of(() -> {
log.info("delayTask.cancel");
})
.plusTime(Duration.ofSeconds(2))
.task();
TimeUnit.MILLISECONDS.sleep(500);
delayTask.isActive(); // true
delayTask.cancel();
delayTask.isActive(); // false
// ------------ taskId ----------
String taskId = "1";
DelayTaskKit.of(taskId, () -> log.info("DelayTaskKit.cancel"))
.plusTime(Duration.ofSeconds(1))
.task();
TimeUnit.MILLISECONDS.sleep(500);
DelayTaskKit.cancel(taskId);
}
Demo - Find Delayed Task
This sample demonstrates finding delayed tasks.
- code 9: find the delayed task by
taskId. - code 15: find delayed task by
taskId; if present, execute given logic.
public void optionalDelayTask() {
String newTaskId = "1";
DelayTaskKit.of(newTaskId, () -> log.info("hello DelayTask"))
.plusTime(Duration.ofSeconds(1))
.plusTime(Duration.ofMillis(1000))
.plusTimeMillis(500)
.task();
Optional<DelayTask> optionalDelayTask = DelayTaskKit.optional(newTaskId);
if (optionalDelayTask.isPresent()) {
DelayTask delayTask = optionalDelayTask.get();
log.info("{}", delayTask);
}
DelayTaskKit.ifPresent(newTaskId, delayTask -> {
// your code
delayTask.plusTimeMillis(500);
});
}
Demo - Enhanced TaskListener
For more details on the TaskListener interface, see TaskKit.
public void customTaskListener() {
DelayTaskKit.of(new TaskListener() {
@Override
public void onUpdate() {
log.info("TaskListener");
}
@Override
public boolean triggerUpdate() {
return TaskListener.super.triggerUpdate();
}
@Override
public Executor getExecutor() {
return TaskListener.super.getExecutor();
}
@Override
public void onException(Throwable e) {
TaskListener.super.onException(e);
}
})
.plusTime(Duration.ofMillis(1700))
.task();
}
Comprehensive Example
A comprehensive sample is used here. Business content:
- Simulate enhancement when a player uses an item.
- When
true, cooldown is reduced by 0.5 seconds and a luck bonus is applied.
The following starts a delayed task. When triggered, ShootTaskListener.onUpdate() executes.
ShootTaskListener simulates bullet attacks on enemies. If an item is used (that is, luck == true), attack increases.
The logic is simple: use a random value to simulate whether an item is used. When true, trigger time is shortened and ShootTaskListener is enhanced.
- code 2: add
ShootTaskListener. - code 3: trigger after 1.5 seconds.
- code 9: get
TaskListener.
public void example() {
DelayTask delayTask = DelayTaskKit.of(new ShootTaskListener("Enemy"))
.plusTimeMillis(1500)
.task();
if (RandomKit.randomBoolean()) {
delayTask.minusTime(Duration.ofMillis(500));
ShootTaskListener shootTaskListener = delayTask.getTaskListener();
shootTaskListener.setLuck(true);
}
}
@Slf4j
final class ShootTaskListener implements TaskListener {
final String targetEntity;
boolean luck;
int attack = 10;
ShootTaskListener(String targetEntity) {
this.targetEntity = targetEntity;
}
@Override
public void onUpdate() {
int value = luck ? attack * 2 : attack;
log.info("value", value);
}
}
Summary
As you can see, lightweight controllable delayed tasks are simple to use in just 3 steps:
- Set listener
TaskListener. - Add delay time.
- Start task.
Developers can apply different controls based on business changes. Since the listener reuses the TaskListener interface, more flexible settings are possible.
For example: specify executor, control whether to trigger onUpdate() callback, exception callback, and more.
If delay time is not set, the task executes immediately.
public void hello() {
DelayTaskKit.of(() -> {
log.info("hello");
}).task();
}