本章导读

Future 是一次性异步结果的标准抽象(第 48 章文件异步、第 59 章执行器、卷七网络 IO 共同的结果货币):xrtPromiseCreate 同时创建一对角色——Promise 写端(生产者发布值或失败)与 Future 读端(消费者等待并读取);终局四态(XFUTURE_RESOLVED 成功 / XFUTURE_FAILED 失败 / XFUTURE_CANCELLED 取消 / XFUTURE_CLOSED 无值收尾);延续(then 式——源 Future 的结果经回调变换、经输出 Promise 发布新值——异步管道的 C 形态);组合子(future_combine 范例的 First 族——多个 Future 中先完成者胜);等待族(Wait/WaitFor/WaitUntil + xrtFutureWatchInit/WatchAdd 回调)。取消令牌(第 54 章)从两端都可取——Future 是取消树的一等公民。

58.1引入

第 48 章异步文件已经见过 Future 的用法面(拿到 Future、Wait、Value)——本章补全它的完整模型。三个问题开路。问题一:异步操作的结果谁来交付——操作方持有 Promise(写端),调用方持有 Future(读端):一对分离的句柄天然匹配"生产者/消费者"两个角色,写端不会误等待自己、读端无法伪造结果。问题二:多个异步步骤怎么串——查缓存(miss)→查库→回写→返回,四步异步:延续让每步的输出成为下一步的输入(result: 105 的 100→+5 就是两次变换),主线只等最终结果。问题三:三个数据源谁快用谁——组合子(First)把三个 Future 合成一个"先到者"的 Future。

三个问题对应 Future 的三层能力:交付模型(Promise/Future 对)、组合(延续与组合子)、等待(族 + Watch)。第 48 章的文件异步、第 59 章的执行器任务、卷七的网络 IO——全部以 Future 为结果货币,本章学的是这门货币的发行规则。

58.2概念

一对角色与三态终局

1创建xrtPromiseCreate(&Future, 父取消令牌) → 写端+读端
2发布xrtPromiseResolve(值)/ Reject(失败+错误)/ Close(无值收尾)
3取消任一端的 Cancel——Cancelled 终局(第 54 章令牌联动)
4读取State 查态 / Wait 族等待 / Value 借用值 / Error 借用错误

一次性是 Future 的身份:终局只发生一次——重复发布被拒绝、终局不可改(Resolved 不会变 Rejected)。终局语义对应第 54 章"取消≠失败"的完整落地:Resolved(成功交付值)、FAILED(失败——xrtPromiseReject 携带第 4 章错误对象)、CANCELLED(控制流放弃)、CLOSED(写端无值收尾——写方销毁前没有结果时的礼貌关闭)。xfutureresult 结构体统一携带三态 + 值/错误——读取侧一个结构看全结局。

延续:then 的 C 形态

延续回调的签名(future_continue 范例):(源结果, 输出Promise, 数据)——读源、算新值、经输出 Promise 发布。链式组合:每环一个回调 + 一个输出——四步异步管道就是四环;主线只在链尾等。与回调地狱的对照:嵌套回调把"步骤"藏在缩进里;延续链把步骤排成一条线——错误沿链传播(某环 Rejected 则下游同态)、取消沿链生效(令牌穿链)。执行时机:延续在哪个线程跑取决于注册处(调度器侧注册则在调度器线程、池侧在池线程)——组合的确定性来自注册的确定性。

组合子:First 与 friends

future_combine 范例(winner[1] = 22 的输出):多个 Future 组合成"先完成者"——赢家的索引与值同时可得。First 的经典场景:多副本读(哪个副本快用哪个)、超时竞争(数据 vs 定时器——先到者胜,超时版即 WaitFor 的组合形态)、冗余请求(三路并发取首个成功)。All 族(等全部)在任务组(第 60 章)的汇合等待里呈现——本章聚焦 First。

等待族与 Watch

形态入口适用
阻塞等待xrtFutureWait简单汇合(第 48 章用法)
限时等待WaitFor / WaitUntil超时控制(deadline 族)
回调通知xrtFutureWatchInit/WatchAdd事件循环(不阻塞——完成时回调)
协程等待调度器协程内 await(第 56 章 Park/Wake)协程直线代码

Watch 的纪律与全库回调一致:轻活(第 38/48 章)——重处理延续到唤醒后的上下文。引用配平:Future/Promise 各自 Ref/Destroy;把手里的引用数交给存它的结构管理(第 5 章纪律)。

取消的三向联动

Promise 侧 CancelToken、Future 侧 CancelToken 都可取(xrtPromiseCancelToken/xrtFutureCancelToken)——取消树穿过整对;xrtFutureCancel 是直达按钮。联动形态:父令牌(第 54 章树)→ 创建时的 pParentCancel 参数——父取消则 Future 终局 Cancelled(不需要手动转发);读端 Cancel 反向通知写端(操作方在 CancelToken 的 Watch 里中止工作——第 54 章检查点纪律)。

桥接:把异步完成接回 Future(future_bridge)

异步库的经典竞态:操作入口要立刻返回一个 Future,而结果要稍后才到——中间这段"装配窗口"里,底层完成回调可能先到、Future 的终态却还不能写(调用方可能还没拿到 Promise 的引用、取消监听还没挂上)。xfuturebridge 就是这段窗口的闸门:xrtFutureBridgeCreate 创建 Future/Promise 对并初始化桥(或 xrtFutureBridgeInit 桥接调用方自己持有的 Promise——桥借用不持有);xrtFutureBridgeWatch 把 Future 的协作取消转发给底层操作;装配完成时二选一发布——xrtFutureBridgeReady 放行(此后完成回调可以写 Promise 终态)、xrtFutureBridgeFail 失败(回调只回收结果不写入);底层线程用 xrtFutureBridgeWait 等这个发布决定,xrtFutureBridgeUnwatch 注销取消监听并与正在执行的取消回调汇合。桥本身是 32 字节固定存储——直接嵌进异步操作的上下文结构,零额外分配。

它的价值在"顺序自由":装配方与完成方谁先到都不出错——Ready 之前的终态写入被挂起,Ready/Fail 决定放行或回收。第 59 章执行器、第 61 章调度骨架、第 99 章网络库的异步入口,底层都是这座桥——读它们的实现时认出这八个函数,竞态处理就不再神秘。

58.3示例

完整程序:一次异步交付

来自仓库范例 examples/concurrency/future/main.c:

examples/concurrency/future/main.c源码 ↗
#include <stdio.h>
#include <xrt.h>



















 */


/* Future/Promise 基础示例直接完成并读取一个借用值。 */
int main(void)
{
	xfuture* pFuture;
	xpromise* pPromise;
	int iValue = 42;

	pPromise = xrtPromiseCreate(&pFuture, NULL);
	if ( (pPromise == NULL) || !xrtPromiseResolve(pPromise, &iValue) ) {
		return 1;
	}
	if ( xrtFutureWait(pFuture) != XWAIT_OK ) {
		return 2;
	}
	printf("future value: %d\n", *(int*)xrtFutureValue(pFuture));
	xrtPromiseDestroy(pPromise);
	xrtFutureDestroy(pFuture);
	return 0;
}
$ gcc -O1 -DXRT_MODULE_ALL -I single -include xrt.h impl.c examples/concurrency/future/main.c -lws2_32 -liphlpapifuture value: 42

刚才发生了什么。 ① xrtPromiseCreate(&pFuture, NULL)——一次创建写读两端(NULL 即不挂父令牌——挂了就是第 54 章的树形联动)。② Promise 端发布值 42——一次性终局 Resolved。③ Future 端 Wait 等待完成、xrtFutureValue 借用读取值(时效到下一次操作——要持有自己 Ref/拷贝)。④ future value: 42 打印——发布→等待→读取的完整交付链。这个 43 行的闭环是全部 Future 用法的原子形态——延续、组合、池任务的结果通道全是它的组合化。

完整程序:延续链

来自 examples/concurrency/future_continue/main.c——then 式两环:

examples/concurrency/future_continue/main.c源码 ↗
#include <stdio.h>
#include <xrt.h>



















 */


/* 延续读取源结果,并通过输出 Promise 发布转换后的值。 */
static void addFive(
	const xfutureresult* pInput,
	xpromise* pOutput,
	ptr pData
)
{
	int* pValue = (int*)pData;

	*pValue = *(int*)pInput->Value + 5;
	(void)xrtPromiseResolve(pOutput, pValue);
}



/* 串联一个短成功延续并读取最终结果。 */
int main(void)
{
	xfuture* pSource;
	xfuture* pNext;
	xpromise* pPromise = xrtPromiseCreate(&pSource, NULL);
	int iSource = 100;
	int iResult = 0;

	if ( pPromise == NULL ) {
		return 1;
	}
	pNext = xrtFutureThen(pSource, addFive, &iResult);
	if ( pNext == NULL ) {
		xrtPromiseDestroy(pPromise);
		xrtFutureDestroy(pSource);
		return 2;
	}
	(void)xrtPromiseResolve(pPromise, &iSource);
	printf("result: %d\n", *(int*)xrtFutureValue(pNext));

	xrtFutureDestroy(pNext);
	xrtPromiseDestroy(pPromise);
	xrtFutureDestroy(pSource);
	return 0;
}
$ gcc -O1 -DXRT_MODULE_ALL -I single -include xrt.h impl.c examples/concurrency/future_continue/main.c -lws2_32 -liphlpapiresult: 105

刚才发生了什么。 ① 延续回调 addFive(源结果, 输出Promise, 数据)——读源 Future 的值、加五、经输出 Promise 发布:输入与输出都是异步的,变换本身也是异步链上的一环。② 链尾等待得到 result: 105(100→+5)——主线没有嵌套、没有中间等待,一条直线等最终值。③ 失败传播:源若 Rejected,延续按约定不发布(或转发错误)——错误沿链到尾(写延续时显式处理源失败——坑区有实例)。④ future_combine 范例是组合子版:多个源中 winner[1](索引 1 的 22)先到——先完成者胜、索引与值齐得。

完整程序:bridge——装配窗口的三步演示

来自仓库范例 examples/concurrency/bridge_tour/main.c:

examples/concurrency/bridge_tour/main.c源码 ↗





















 */

#include <stdio.h>
#include <stdbool.h>
#include <xrt.h>

/* 取消回调:记录被转发的事实。 */
static void exampleCancel(ptr pData)
{
	bool* pCancelled = (bool*)pData;

	*pCancelled = true;
}

int main(void)
{
	xfuturebridge Bridge;
	xfuturebridge Bridge2;
	xfuture* pFuture = NULL;
	xfuture* pFuture2 = NULL;
	xpromise* pPromise2 = NULL;
	xpromise* pBorrowed = NULL;
	bool bCancelled = false;
	int iResult = 1;

	/* ---- Create:Future 与桥一体创建,Promise 可借用。 ---- */
	pFuture = xrtFutureBridgeCreate(&Bridge, NULL);
	if ( (pFuture == NULL) ||
		((pBorrowed = xrtFutureBridgePromise(&Bridge)) == NULL) ) {
		goto Cleanup;
	}
	printf("bridge: create + promise borrow ok\n");

	

	 * 单线程演示中先 Ready 再 Wait 立即返回。 ---- */
	if ( !xrtFutureBridgeWatch(&Bridge, exampleCancel,
			(ptr)&bCancelled) ||
		!xrtFutureBridgeReady(&Bridge) ||
		!xrtFutureBridgeWait(&Bridge) ||
		!xrtPromiseResolve(pBorrowed, (ptr)42) ||
		(xrtFutureWaitFor(pFuture, 1000) != XWAIT_OK) ||
		(xrtFutureValue(pFuture) != (ptr)42) ) {
		goto Cleanup;
	}
	xrtFutureBridgeUnwatch(&Bridge);
	printf("bridge: watch + ready -> resolve -> future value ok\n");

	/* ---- Init 挂到自有 Promise + Fail 回收:Wait 为假。 ---- */
	pPromise2 = xrtPromiseCreate(&pFuture2, NULL);
	if ( (pPromise2 == NULL) ||
		(pFuture2 == NULL) ||
		!xrtFutureBridgeInit(&Bridge2, pPromise2) ||
		(xrtFutureBridgePromise(&Bridge2) != pPromise2) ||
		!xrtFutureBridgeFail(&Bridge2) ||
		xrtFutureBridgeWait(&Bridge2) ) {
		goto Cleanup;
	}
	
	 * 所有,终态写入仍然合法(此处用 Resolve 收口 Future)。 */
	if ( !xrtPromiseResolve(pPromise2, (ptr)1) ||
		(xrtFutureWaitFor(pFuture2, 1000) != XWAIT_OK) ) {
		goto Cleanup;
	}
	printf("bridge: init on own promise + fail -> wait false ok\n");
	iResult = 0;

Cleanup:
	xrtFutureBridgeUnwatch(&Bridge2);
	xrtPromiseDestroy(pPromise2);
	xrtFutureDestroy(pFuture2);
	xrtFutureBridgeUnwatch(&Bridge);
	xrtFutureDestroy(pFuture);
	return iResult;
}
$ gcc -O1 -DXRT_MODULE_ALL -I single -include xrt.h impl.c examples/concurrency/bridge_tour/main.c -lws2_32 -liphlpapibridge: create + promise borrow okbridge: watch + ready -> resolve -> future value okbridge: init on own promise + fail -> wait false ok

刚才发生了什么。 ① Create 一步拿到 Future 与桥,xrtFutureBridgePromise 借出写端——所有权仍在调用方,销毁责任不变。② Watch(挂取消转发)→ Ready(放行)→ Promise 写终态 → Future 读到值——顺序刻意摆成"装配先完成";真实异步里这三步可能以任何顺序交错,桥都吃得下。③ 第二座桥演示失败路径:Init 桥接自己的 Promise、发布 Fail——此后 Wait 返回假,完成回调只回收不写入。取消回调(exampleCancel)在 Unwatch 汇合前被转发的事实也一并演示。

58.4契约

  • 一次性:终局恰好一次、不可改;重复发布被拒。
  • 三态终局:Resolved/Rejected/Cancelled——xfutureresult 统一携带;取消≠失败(第 54 章)。
  • 借用读取:Value/Error 是借用——立即用或自己持有;Destroy 前用完。
  • 延续纪律:显式处理源三态(失败转发或降级——不吞不崩);输出必发布(否则下游悬挂)。
  • 引用配平:两端各自 Ref/Destroy;存进结构的引用由结构管(第 5 章所有权纪律)。
  • 取消联动:创建挂父令牌则自动联动;两端 CancelToken 各自可取;操作方在令牌 Watch 里中止工作。
  • 桥契约:Ready 前终态写入挂起、Fail 后只回收;桥借用 Promise 不持有;Unwatch 与取消回调汇合后桥才可弃置。

常见错误

坑 1:延续吞掉源失败

症状:链尾永远等不到结果(悬挂)或拿到零值当成功——上游失败被延续静默丢弃。

原因:延续回调只写了成功分支——源的 Rejected/CANCELLED 没有转发给输出 Promise。

错误写法
static void step(const xfutureresult* pIn, xpromise* pOut, ptr pData)
{
	if ( pIn->State == XFUTURE_RESOLVED ) {
		xrtPromiseResolve(pOut, Transform(pIn));   /* 只处理成功 */
	}
	/* 失败分支缺失——pOut 永远不发布——下游悬挂 */
}
正确写法
static void step(const xfutureresult* pIn, xpromise* pOut, ptr pData)
{
	if ( pIn->State == XFUTURE_RESOLVED ) {
		xrtPromiseResolve(pOut, Transform(pIn));
	} else {
		ForwardTerminal(pOut, pIn);   /* 按源 State 调 Reject 或 Cancel——链不断 */
	}
}
坑 2:Value 读到旧值或悬空

症状:读到上一次的值、或完成前的垃圾值;偶发崩溃(值指向已释放内存)。

原因:没有终局就读值(一次性结果未就绪)、或把借用值存过了 Future 的 Destroy/下一次操作。

错误写法
ptr Value = xrtFutureValue(pFuture);   /* 未等待——终局前读值是未定义 */
Save(Value);                            /* 借用指针存起来——悬空窗口 */
正确写法
if ( xrtFutureWait(pFuture) != XWAIT_OK ) { return false; }
ptr Value = xrtFutureValue(pFuture);   /* 终局后借用 */
Use(Value);                             /* 立即用——或拷贝后持有 */

编程练习

基础:三态制造机

Promise 分别 Resolve 值 / Reject 失败 / Cancel 取消 / Close 收尾四种终局——读端 Wait 后打印 State 与值/错误——四态各验证一次(读端 State 与值/错误逐项打印)。

进阶:四环管道

查缓存→miss 查库→变换→回写:四环延续链(每环 mock 一个异步延迟)——链尾等最终值;中间一环注入失败验证传播到尾。

挑战:多副本读取器

三个"副本"Future(各自 mock 不同延迟)+ First 组合——取首个返回者;败者取消(令牌联动)。验收标准:成功路径拿到最快副本值、失败路径(全败)得到聚合错误;败者资源收尾(第 6 章统计验证零泄漏);取消树传播可审计。

小结

知识点速查
一对角色Promise 写端(发布)/ Future 读端(等待读取)——Create 一次成对
三态终局Resolved/Rejected/CANCELLED;xfutureresult 统一携带;一次不可改
延续回调(源结果,输出Promise,数据)——显式转发失败、输出必发布
组合子First 先到者胜(赢家索引+值同得);All 在第 60 章任务组
等待族Wait/For/Until 限时 + Watch 无分配回调 + 协程 await(第 56 章)
取消联动创建挂父令牌自动联动;两端各可取 CancelToken
借用纪律Value/Error 是借用——立即用或拷贝持有,Destroy 前用完
装配桥Ready 放行/Fail 回收;Watch 转发取消;32 字节可嵌入上下文

📖 本章涉及的 API 参考手册:future(85 个条目)cancel(18 个条目)future_bridge(11 个条目)

点击查看完整签名、参数约束、返回值与错误说明