接口 ICancelToken

所有已知子接口:
ICancelTokenSource

public interface ICancelToken
取消令牌

取消令牌由任务持有,任务在执行期间可主动检测取消,并在检测到取消后抛出CancellationException, 如果不想打印堆栈,可抛出StacklessCancellationException

首先,我们要明白为什么需要支持取消,当不再需要任务的结果时,及时取消任务的执行,以避免不必要的资源浪费。
在多年以前,多线程编程和异步编程尚不普遍,因此Future通常只与单个任务绑定,因此取消任务的最佳方式就是通过Future取消 —— 既清晰,又可以避免额外开销。 但在异步编程如火如荼的今天,通过Future取消任务越来越力不从心。

Future.cancel 的缺陷

1. Future.cancel(boolean)的接口约定是强制的,要求方法返回前Future必须进入完成状态,这是个错误的约定。 取消是协作式的,并不能保证立即成功 -- 取消一个任务和终止一个线程没有本质区别。
2. 通过Future取消只能取消Future关联的任务,而不能取消一组相关的任务。要取消一组相关的任务,必须让这些任务共享同一个上下文 -- 即取消上下文。

如何中断线程

注意!取消令牌只是一个共享上下文,不具备任何其它功能。一个任务如果要响应中断信号,必须注册监听器,然后中断自身所在的线程。

   public void run() {
      // 在执行耗时操作前检查取消信号
      cancelToken.checkCancel();
      // 在执行阻塞操作前监听取消信号以唤醒线程
      Thread thread = Thread.currentThread();
      var handle = cancelToken.thenAccept(token -> {
          thread.interrupt();
      })
      // 如果handle已被通知,那么线程已处于中断状态,阻塞操作会立即被中断
      try (handle) {
          blockingOp();
      }
   }
 

监听器

1. accept系列方法表示接收token参数;run方法表示不接收token参数; 2. async表示目标action需要异步执行,方法的首个参数为executor;

监听器管理

用户除了可以通过返回的Registration删除监听器外, 还可以通过调度选项TaskOptions.STAGE_LISTEN_CANCEL_TOKEN要求当前取消令牌监听用户上下文中的取消令牌, 以及时删除不再需要执行的回调 -- 可以实现批量删除。

差异的源头: 取消令牌可能永远不会收到取消信号,而这可能是正常的需求,因此用户的监听器必须及时从监听器列表删除; 虽然不及时删除不会导致逻辑错误,但会导致内存泄漏。

作者:
wjybxx date - 2024/1/8