Chapter Orientation

The Future is the standard abstraction of the one-shot asynchronous result (the shared result currency of Chapter 48's async files, Chapter 59's executor, and Volume 7's network IO): xrtPromiseCreate creates a pair of roles at once — the Promise write end (the producer publishes a value or a failure) and the Future read end (the consumer waits and reads); a four-state endgame (XFUTURE_RESOLVED success / XFUTURE_FAILED failure / XFUTURE_CANCELLED cancellation / XFUTURE_CLOSED valueless close); continuations (then-style — the source Future's result transforms through a callback and publishes a new value through an output Promise — the C form of async pipelines); combinators (the First family of the future_combine sample — among several Futures, the first to finish wins); and the wait family (Wait/WaitFor/WaitUntil plus the xrtFutureWatchInit/WatchAdd callbacks). The cancellation token (Chapter 54) is reachable from both ends — the Future is a first-class citizen of the cancellation tree.

58.1引入

Chapter 48's async files already showed Future's usage surface (get a Future, Wait, Value) — this chapter completes its full model. Three questions lead. Question one: who delivers the async operation's result — the operator holds the Promise (write end), the caller holds the Future (read end): a pair of separated handles naturally matches the "producer/consumer" roles; the write end cannot mistakenly wait on itself, the read end cannot forge a result. Question two: how are several async steps strung together — check cache (miss) → query database → write back → return, four async steps: continuations make each step's output the next step's input (result: 105's 100→+5 is two transformations), the main line waits only for the final result. Question three: three data sources, use whichever is fastest — a combinator (First) fuses three Futures into one "first to arrive" Future.

The three questions map to Future's three capability layers: the delivery model (the Promise/Future pair), composition (continuations and combinators), waiting (the family + Watch). Chapter 48's file async, Chapter 59's executor tasks, Volume 7's network IO — all use Future as the result currency; this chapter teaches the issuance rules of that currency.

58.2概念

A pair of roles and the three-state endgame

1CreatexrtPromiseCreate(&Future, parent cancel token) -> write end + read end
2PublishxrtPromiseResolve (value) / Reject (failure + error) / Close (valueless finish)
3CancelCancel from either end - the Cancelled endgame (Chapter 54 token linkage)
4ReadState queries / the Wait family waits / Value borrows the value / Error borrows the error

One-shot is the Future's identity: the endgame happens exactly once — repeated publishing is refused, the endgame is immutable (Resolved never becomes Rejected).Endgame semantics land Chapter 54's "cancellation ≠ failure" in full: Resolved (value delivered), FAILED (failure — xrtPromiseReject carries Chapter 4's error object), CANCELLED (control-flow abandonment), CLOSED (the write end's valueless finish — the polite close when the writer destroys without a result). The xfutureresult struct uniformly carries the three states + value/error — the reading side sees the whole ending in one structure.

Continuations: the C form of then

The continuation callback's signature (the future_continue sample): (源结果, 输出Promise, 数据) (source result, output Promise, data) — read the source, compute the new value, publish through the output Promise.Chained composition: one callback plus one output per link — a four-step async pipeline is four links; the main line waits only at the chain's tail.Against callback hell: nested callbacks hide "steps" in indentation; the continuation chain lays the steps in a line — errors propagate along the chain (one link Rejected puts downstream in the same state), cancellation takes effect along the chain (the token threads it).Execution timing: which thread a continuation runs on is decided by where it is registered (scheduler-side registration runs on the scheduler's thread, pool-side on the pool's) — composition's determinism comes from registration's determinism.

Combinators: First and friends

The future_combine sample (winner[1] = 22 in the output): several Futures combined into "the first to finish" — the winner's index and value are both available. First's classic scenes: multi-replica reads (use whichever replica is fastest), timeout races (data versus timer — first arrival wins; the timeout version is WaitFor's composed form), redundant requests (three concurrent paths, take the first success). The All family (wait for all) appears in Chapter 60's task-group join waits — this chapter focuses on First.

The wait family and Watch

ShapeEntranceFits
Blocking waitxrtFutureWaitsimple joins (Chapter 48's usage)
Timed waitWaitFor / WaitUntiltimeout control (the deadline family)
Callback notificationxrtFutureWatchInit/WatchAddevent loops (non-blocking - callback on completion)
Coroutine waitawait inside a scheduler coroutine (Chapter 56's Park/Wake)straight-line coroutine code

Watch's discipline matches the library-wide callback rule: light work (Chapters 38/48) — heavy processing continues in the context after wakeup.Reference balancing: Future and Promise each Ref/Destroy; hand the reference count of what you store to the structure holding it (Chapter 5's discipline).

Cancellation's three-way linkage

Both the Promise side and the Future side expose a CancelToken (xrtPromiseCancelToken/xrtFutureCancelToken) — the cancellation tree passes through the pair; xrtFutureCancel is the direct button. The linkage shape: a parent token (Chapter 54's tree) → the pParentCancel parameter at creation — a parent cancellation ends the Future Cancelled (no manual forwarding needed); the read end's Cancel notifies the write end in reverse (the operator aborts work in the CancelToken's Watch — Chapter 54's checkpoint discipline).

Bridging: wiring async completion back into a Future (future_bridge)

A classic race of asynchronous libraries: the entry point must return a Future immediately, while the result arrives later — in that "assembly window", the underlying completion callback may arrive before the Future's terminal state may be written (the caller may not yet hold the Promise reference; the cancel listener may not be attached). xfuturebridge is the gate for exactly that window: xrtFutureBridgeCreate creates the Future/Promise pair and initializes the bridge (or xrtFutureBridgeInit bridges a Promise the caller already owns — the bridge borrows, never owns); xrtFutureBridgeWatch forwards the Future's cooperative cancellation to the underlying operation; when assembly completes, publish one of two ways — xrtFutureBridgeReady opens the gate (the terminal state settles) (completion callbacks may now write the Promise's terminal state), xrtFutureBridgeFail declares a degradation to failure (callbacks only reclaim the result, writing nothing); the worker thread uses xrtFutureBridgeWait to wait for that decision, and xrtFutureBridgeUnwatch unregisters the cancel listener and joins any cancel callback still executing. The bridge itself is 32 bytes of fixed storage — embed it directly in the async operation's context struct, zero extra allocation.

Its value is order freedom: whichever of assembler and completer arrives first, no race condition arises — terminal writes before Ready are suspended; Ready/Fail decides release or reclaim. Chapter 59's executor, Chapter 61's scheduling skeleton, and Chapter 99's network async entries all stand on this bridge underneath — recognize these eight functions when reading their implementations and the race handling stops being mysterious.

58.3示例

Complete program: one asynchronous delivery

From the repository sample 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

What just happened. (1) xrtPromiseCreate(&pFuture, NULL) — one creation of both ends (NULL means no parent token — attach one for Chapter 54's tree linkage). (2) The Promise end publishes the value 42 — the one-shot endgame Resolved. (3) The Future end Waits for completion; xrtFutureValue borrow-reads the value (valid until the next operation — to hold it, Ref/copy it yourself). (4) future value: 42 prints — the complete delivery chain of publish → wait → read.This 43-line loop is the atomic form of all Future usage — continuations, combinators, and pool-task result channels are all compositions of it.

Complete program: a continuation chain

From examples/concurrency/future_continue/main.c — then-style, two links:

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

What just happened. (1) The continuation callback addFive(源结果, 输出Promise, 数据) (source result, output Promise, data) — reads the source Future's value, adds five, publishes through the output Promise: input and output are both asynchronous, and the transformation itself is a link in the async chain. (2) Waiting at the chain's tail yields result: 105 (100→+5) — the main line has no nesting, no intermediate waits, one straight line to the final value. (3) Failure propagation: if the source were Rejected, the continuation by convention does not publish (or forwards the error) — the error runs down the chain to the tail (when writing continuations, handle source failure explicitly — the pitfalls hold an example). (4) The future_combine sample is the combinator edition: among several sources, winner[1] (22 at index 1) arrives first — first-finisher wins, index and value both in hand.

Complete program: bridge — a three-step tour of the assembly window

From the repository example 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

What just happened. (1) Create yields the Future and bridge in one step; xrtFutureBridgePromise lends the write end — ownership stays with the caller, destruction duty unchanged. (2) Watch (attach cancel forwarding) → Ready (open the gate) → Promise writes the terminal state → the Future reads the value — the order deliberately stages "assembly first"; in real async these three steps may interleave any way, and the bridge absorbs all of it. (3) A second bridge demonstrates the failure path: Init onto its own Promise, publish Fail — thereafter Wait returns false and completion callbacks only reclaim, never write. The cancel callback (exampleCancel) also demonstrates the fact of forwarding before Unwatch joins it.

58.4契约

  • One-shot: the endgame exactly once, immutable; repeated publishing refused.
  • Three-state endgame: Resolved/Rejected/Cancelled — uniformly carried by xfutureresult; cancellation ≠ failure (Chapter 54).
  • Borrowed reads: Value/Error are borrows — use immediately or hold your own copy; used up before Destroy.
  • Continuation discipline: handle the source's three states explicitly (forward failure or degrade — never swallow, never crash); the output must publish (else the downstream hangs).
  • Reference balancing: both ends Ref/Destroy each; references stored into structures are managed by the structure (Chapter 5's ownership discipline).
  • Cancellation linkage: attaching a parent token at creation links automatically; both ends expose CancelToken; the operator aborts work in the token's Watch.
  • Bridge contract: terminal writes suspend before Ready, only reclaim after Fail; the bridge borrows the Promise without owning it; Unwatch joins the cancel callback before the bridge may be discarded.

Common Mistakes

Pitfall 1: the continuation swallows the source's failure:Pitfall 1: the continuation swallows the source's failure

Symptom: the chain's tail never gets a result (hangs) or receives a zero value as success — an upstream failure silently dropped by the continuation.

Cause: the continuation callback wrote only the success branch — the source's Rejected/CANCELLED was never forwarded to the output Promise.

错误写法
static void step(const xfutureresult* pIn, xpromise* pOut, ptr pData)
{
	if ( pIn->State == XFUTURE_RESOLVED ) {
		xrtPromiseResolve(pOut, Transform(pIn));   /* success only */
	}
	/* the failure branch is missing - pOut never publishes - the downstream hangs */
}
正确写法
static void step(const xfutureresult* pIn, xpromise* pOut, ptr pData)
{
	if ( pIn->State == XFUTURE_RESOLVED ) {
		xrtPromiseResolve(pOut, Transform(pIn));
	} else {
		ForwardTerminal(pOut, pIn);   /* Reject or Cancel per the source State - the chain stays unbroken */
	}
}
Pitfall 2: Value reads a stale or dangling value:Pitfall 2: Value reads a stale or dangling value

Symptom: reading the previous value, or garbage from before completion; occasional crashes (the value pointing into freed memory).

Cause: reading the value before the endgame (the one-shot result not ready), or storing the borrowed value past the Future's Destroy/next operation.

错误写法
ptr Value = xrtFutureValue(pFuture);   /* no wait - reading before the endgame is undefined */
Save(Value);                            /* storing the borrowed pointer - a dangling window */
正确写法
if ( xrtFutureWait(pFuture) != XWAIT_OK ) { return false; }
ptr Value = xrtFutureValue(pFuture);   /* borrowed after the endgame */
Use(Value);                             /* use immediately - or copy and hold */

Programming Exercises

Basic: a four-state machine

The Promise respectively Resolve a value / Reject a failure / Cancel / Close — the read end Waits then prints State and value/error — all four states verified once (the read end's State and value/error itemized).

Advanced: a four-link pipeline

Check cache → on miss query DB → transform → write back: a four-link continuation chain (each link mocks an async delay) — wait at the chain's tail for the final value; inject a failure in a middle link and verify propagation to the tail.

Challenge: a multi-replica reader

Three "replica" Futures (each mocking a different delay) + a First combinator — take the first returner; cancel the losers (token linkage). Acceptance criteria: the success path yields the fastest replica's value, the failure path (all fail) an aggregate error; the losers' resources drained (Chapter 6's statistics verify zero leaks); the cancellation-tree propagation auditable.

Summary

TopicQuick reference
Pair of rolesPromise write end (publishes) / Future read end (waits and reads) - Create pairs them at once
Three-state endgameResolved/Rejected/CANCELLED; xfutureresult carries them uniformly; once, immutable
Continuationscallback(source result, output Promise, data) - forward failures explicitly, always publish the output
CombinatorsFirst - first arrival wins (winner index + value together); All is Chapter 60's task group
Wait familyWait/For/Until timed + Watch zero-allocation callback + coroutine await (Chapter 56)
Cancellation linkageattach a parent token at creation for auto-linkage; both ends expose CancelToken
Borrowing disciplineValue/Error are borrows - use immediately or copy and hold, used up before Destroy
Assembly bridgeReady releases / Fail reclaims; Watch forwards cancellation; 32 bytes, embeddable in a context

📖 API references in this chapter:future (85 entries)cancel (18 entries)future_bridge (11 entries)

Click for full signatures, parameter constraints, return values and error notes