Skip to main content

轻量可控的延时任务

介绍

我们知道,在 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();
}

综合示例

这里使用一个综合示例来演示,示例业务内容

  1. 模拟玩家使用某种道具时的增强。
  2. 当为 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 步

  1. 设置监听器 TaskListener
  2. 添加延时时间
  3. 启动任务

开发者可以根据业务的变化对任务进行不同的控制,因为监听器沿用的 TaskListener 接口,所以可以做更灵活的设置。 比如,指定执行器、是否触发 onUpdate() 监听回调方法、异常回调 ...等。

tip

如果不设置延时时间,则会立即执行任务。

public void hello() {
DelayTaskKit.of(() -> {
log.info("hello");
}).task();
}