轻量可控的延时任务
介绍
我们知道,在 TaskKit, 提供了一个任务、时间、延时监听、超时监听 ...等相结合的一个工具模块。通过 runOnce 可以执行一些延时任务,如下所示
TaskKit.runOnce(() -> log.info("2 Seconds"), 2, TimeUnit.SECONDS);
TaskKit.runOnce(() -> log.info("1 Minute"), 1, TimeUnit.MINUTES)
但有时,我们需要一些可控的延时任务,也就是延时时间可以根据后续的业务来变化, 比如增加延时时间、减少延时时间、取消任务 ...等可控的操作。
轻量可控的延时任务 - 特点
- 单一职责原则。
- 任务到达指定时间后会执行。
- 任务可取消。
- 任务可被覆盖。
- 任务可增加延时的时间。
- 任务可减少延时的时间。
- 可设置任务监听回调。
- 内部使用 Netty HashedWheelTimer,轻松支持百万任务。
使用场景
- 场景一:玩家使用某种道具时,减少冷却时间;或者触发某些事件,增加冷却时间...等。 在延时任务运行期间,我们可以为玩家做一些临时性的增强,如临时增加双倍攻击 ...等。
- 场景二:在游戏开始前,房间创建后需要在1分钟后自动解散房间(将来某个时间执行该任务)。 在房间解散前,每当有一个玩家加入房间,则解散房间延迟 30秒(增加延时时间)。 当房间人数大于5人时,则取消房间自动解散规则(取消任务)
以上两个业务场景大体想描述的是,延时任务需要支持延时时间的增减,需要支持取消。
使用示例
演示 - 延时任务
示例任务将在 1 秒后执行。
- code 6,增加 1 秒的延时
- code 7,启动任务
public void runDelayTask() {
DelayTaskKit.of(() -> {
// your code
log.info("1 seconds");
})
.plusTime(Duration.ofSeconds(1))
.task();
}
演示 - 增加延时时间
示例将演示增加延时时间,最终在 1.5 秒后执行延时任务。
- code 7,增加 1 秒的延时
- code 10,增加 0.5 秒的延时
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);
}
演示 - 减少延时时间
示例将演示减少延时时间,最终在 0.5 秒后执行延时任务。
- code 8,增加 1 秒的延时
- code 11,减少 0.5 秒的延时
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);
}
演示 - 覆盖延时任务
示例将演示覆盖延时任务。
- code 5,启动任务
- code 11,因为 taskId 相同,所以会覆盖之前的延时任务,被覆盖的任务会被删除。
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();
}
演示 - 取消延时任务
示例将演示取消延时任务。
- code 11,通过 DelayTask 来取消任务
- code 22,通过 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);
}
演示 - 查找延时任务
示例将演示查找延时任务。
- code 9,通过 taskId 查找该延时任务
- code 15,通过 taskId 查找延时任务,存在则执行给定逻辑
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);
});
}
演示 - 增强 TaskListener
关于 TaskListener 接口更详细的介绍,可阅读 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();
}
综合示例
这里使用一个综合示例来演示,示例业务内容
- 模拟玩家使用某种道具时的增强。
- 当为 true 时,冷却减 0.5 秒,并且幸运加成
下面启动一个延时任务,当触发任务时,会执行 ShootTaskListener onUpdate()
方法。
ShootTaskListener 的业务是模拟子弹攻击敌人,如果使用了道具(即 luck
为 true),那么攻击力提升。
业务比较简单,我们用一个随机值来模拟是否使用了道具,当为 true 时,任务触发时间缩短,并且 ShootTaskListener
增强。
- code 2,add ShootTaskListener。
- code 3,1.5 秒后触发。
- code 9,getTaskListener。
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);
}
}
小结
可以看到,轻量可控的延时任务在使用上是简单的,只需 3 步
- 设置监听器 TaskListener
- 添加延时时间
- 启动任务
开发者可以根据业务的变化对任务进行不同的控制,因为监听器沿用的 TaskListener 接口,所以可以做更灵活的设置。 比如,指定执行器、是否触发 onUpdate() 监听回调方法、异常回调 ...等。
提示
如果不设置延时时间,则会立即执行任务。
public void hello() {
DelayTaskKit.of(() -> {
log.info("hello");
}).task();
}