# 协作式取消

`import thread.cancellation;`

取消等待是让等待者在收到取消请求后返回，而不是终止持锁者、强杀线程或关闭正在等待的句柄。取消已经开始的工作，则需要业务代码在合适的位置检查并清理资源。

本库只提供一次性取消源、可序列化描述符和只读观察者。不提供 reset、回调注册、Job 管理或事务回滚。

## 快速上手：取消 mutex 等待

```aardio
import thread.cancellation;
import process.mutex;

var source = thread.cancellation();
var observer = thread.cancellation.open(source.token);
var mutex = process.mutex();

// 示例模拟请求已取消；实际可由另一个线程或进程请求。
source.cancel();
var acquired,status,code = mutex.wait(5000,observer);
if(acquired){
    // 包括 status == "abandoned"：也已取得所有权。
    // 业务异常路径同样必须释放；close 可兜底本对象记录的次数。
    mutex.release();
}
else print(status); // cancelled

mutex.close();
observer.close();
source.close();
```

### mutex.wait / waitOne 的可选参数

`mutex.wait(timeout, cancelEvent)` 是阻塞等待，不处理窗口消息。

`mutex.waitOne(timeout, cancelEvent)` 在 UI 线程中处理消息；非 UI 或收到退出消息后仍保持 wait-any 语义。

`cancelEvent` 可以是：

- 手动复位的 `thread.event` 对象；
- `thread.cancellation.open(token)` 返回的观察者；
- 借用的有效事件句柄。

省略或传入 `null` 保持旧行为；**描述符本身不是事件对象，须先 open**。

| 结果 | wait 首返回值 | waitOne 首返回值 | 第二返回值 |
|---|---|---|---|
| 取得 mutex | `true` | `1` | `null` |
| 取得已弃置 mutex | `true` | `1` | `"abandoned"` |
| 取消 | `false` | `null` | `"cancelled"` |
| 超时 | `false` | `null` | `"timeout"` |
| 失败 | `false` | `null` | `"failed"` |

超时/原生失败的第三返回值是等待返回码（例如 258、0xFFFFFFFF），不是 `GetLastError()`；取消没有伪造的原生返回码。正常与弃置取得都会登记次数，取消/超时/失败不登记。

取消事件位于等待数组的第一项，两个条件同时就绪时取消优先。**这是等待时的选择，不是取消与所有权之间的事务**：如果已经取得 mutex，随后才到达取消请求，本次成功仍有效，业务方负责检查与释放。

每个对象只兜底自己显式取得的次数。提取裸句柄后绕过对象取得所有权，由提取者负责配对释放；本库不追踪任意跨包装对象操作。

## 跨线程：只传描述符

```aardio
import thread.cancellation;

var source = thread.cancellation();
var ready = thread.event(,true,false);
var worker = thread.create(function(token,ready){
    import thread.cancellation;
    var observer,err = thread.cancellation.open(token);
    if(!observer) return 2;
    ready.set();
    var cancelled = observer.wait(3000);
    observer.close();
    if(cancelled) return 1;
    return 0;
},source.token,ready);

ready.wait(2000);
source.cancel();
thread.wait(worker,4000);
print(thread.getExitCode(worker)); // 1：已观察到取消
raw.closeHandle(worker);
ready.close();
source.close(); // 保持 source 存活直到 worker 结束
```

描述符是 `{schema=1;eventName="..."}`，可经 JSON 编解码后交给其他进程。不要修改内容，不直接传递 source/observer 包装对象。观察者在自己的线程调用 `thread.cancellation.open(token)`；需要发出请求的一方可调用 `thread.cancellation.cancel(token)`。

同一登录会话、具备相应权限的进程可共享；默认使用 `Local\` 命名空间。跨身份/会话的权限配置不是本库职责。

## API 与生命周期

- `thread.cancellation()`：每个任务创建一个新的取消源。`source.token` 是纯数据，不持有内核引用。
- `source.cancel()` / `thread.cancellation.cancel(token)`：请求取消，成功 `true`；重复调用安全。失败返回 `null,错误信息[,系统代码]`。
- `thread.cancellation.open(token)`：只打开已有事件，返回观察者。失败返回 `null,错误信息[,系统代码]`，绝不重建失效令牌。
- `observer.isCancelled()`：已取消 `true`，未取消 `false`，检查失败 `null,状态,原生代码`。不要把 `null` 当作未取消。
- `observer.wait(timeout)` / `waitOne(timeout)`：等待取消信号，返回形状与 `thread.wait` / `thread.waitOne` 一致。这里的等待成功表示**收到取消请求**，不是任务结束。
- `source.close()` / `observer.close()`：关闭各自拥有的句柄，可重复调用，有 GC 兜底，无返回值。等待期间不能关闭相关句柄。

取消源持有事件引用，观察者各持独立、不可继承的 `SYNCHRONIZE` 句柄，不具备 SetEvent/ResetEvent 权限。已取消的事件始终保持有信号；当前及后来打开的多个观察者都能收到取消。

`close` 不等于 `cancel`。源关闭后，已打开的观察者仍然有效；所有引用关闭后事件消失，旧描述符将打开失败。因此控制方应保留源直到全部 worker 结束，避免尚未 open 的 worker 失去事件。

`observer.handle` 仅供借用，不能自行关闭或改写。描述符可用于请求取消，应只交给可信参与方；这不是防恶意进程的安全沙箱，不保证其他直接访问 Win32 API 的代码不能重置或重建同名对象。
