Ориентация главы

Future — стандартная абстракция одноразового асинхронного результата (общая валюта результатов главы 48 о файловой асинхронности, главы 59 об исполнителе и тома 7 о сетевом IO): xrtPromiseCreate одной операцией создаёт пару ролей — конец записи Promise (производитель публикует значение или отказ) и конец чтения Future (потребитель ждёт и читает); финал из четырёх состояний (XFUTURE_RESOLVED успех / XFUTURE_FAILED отказ / XFUTURE_CANCELLED отмена / XFUTURE_CLOSED беззначный финал); продолжения (then-стиль — результат исходного Future преобразуется обратным вызовом и публикуется новым значением через выходной Promise — C-форма асинхронного конвейера); комбинаторы (семейство First образца future_combine — из нескольких Future побеждает завершившийся первым); семейство ожидания (Wait/WaitFor/WaitUntil + обратные вызовы xrtFutureWatchInit/WatchAdd). Токен отмены (глава 54) доступен с обоих концов — Future гражданин первого класса дерева отмены.

58.1引入

Файловая асинхронность главы 48 уже показала поверхность употребления (получил Future, Wait, Value) — эта глава достраивает полную модель. Три вопроса открывают путь. Вопрос первый: кто доставляет результат асинхронной операции — оператор держит Promise (конец записи), вызывающий держит Future (конец чтения): пара разделённых дескрипторов естественно совпадает с ролями «производитель/потребитель» — конец записи не может по ошибке ждать сам себя, конец чтения не может подделать результат. Вопрос второй: как сцепить несколько асинхронных шагов — проверить кеш (промах) → запросить базу → отложить → вернуть, четыре шага: продолжения делают выход каждого шага входом следующего (result: 105: 100→+5 — два преобразования), главная линия ждёт только финального результата. Вопрос третий: из трёх источников данных — кто быстрее, того и берём — комбинатор (First) сливает три Future в один «первый пришедший».

Три вопроса соответствуют трём уровням возможностей Future: модель доставки (пара Promise/Future), композиция (продолжения и комбинаторы), ожидание (семейство + Watch). Файловая асинхронность главы 48, задачи исполнителя главы 59, сетевой IO тома 7 — всё ходит на 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 единообразно несёт три состояния + значение/ошибку — читающая сторона видит всю развязку одной структурой.

Продолжения: C-форма then

Сигнатура обратного вызова продолжения (образец future_continue): (源结果, 输出Promise, 数据) (исходный результат, выходной Promise, данные) — прочитать источник, вычислить новое значение, опубликовать через выходной Promise. Цепочечная композиция: на каждое звено один обратный вызов и один выход — четырёхшаговый асинхронный конвейер это четыре звена; главная линия ждёт только хвоста. Контраст с адом обратных вызовов: вложенные вызовы прячут «шаги» в отступах; цепочка продолжений выстраивает шаги в линию — ошибки идут по цепи (звено Rejected — все ниже в том же состоянии), отмена действует по цепи (токен прошивает её). Момент исполнения: в каком потоке бежит продолжение, решает место регистрации (на стороне планировщика — в потоке планировщика, на стороне пула — в потоке пула) — определённость композиции происходит из определённости регистрации.

Комбинаторы: First и друзья

Образец future_combine (winner[1] = 22 в выводе): несколько Future компонуются в «завершившегося первым» — индекс и значение победителя доступны одновременно. Классические сцены First: чтение со многих реплик (какая быстрее, та и работает), гонка с тайм-аутом (данные против таймера — кто раньше; версия с тайм-аутом и есть скомпонованная форма WaitFor), избыточные запросы (три пути конкурентно, первый успех). Семейство All (ждать всех) появится в сводящем ожидании групп задач главы 60 — здесь фокус на First.

Семейство ожидания и Watch

ФормаВходПрименимость
Блокирующее ожиданиеxrtFutureWaitпростое сведение (употребление главы 48)
Ожидание со срокомWaitFor / WaitUntilконтроль тайм-аутов (семейство deadline)
Уведомление обратным вызовомxrtFutureWatchInit/WatchAddцикл событий (без блокировки - обратный вызов по завершении)
Ожидание в корутинеawait внутри корутины планировщика (Park/Wake главы 56)прямой код корутин

Дисциплина Watch совпадает с общебиблиотечной для обратных вызовов: лёгкая работа (главы 38/48) — тяжёлая обработка продолжается в контексте после пробуждения. Балансировка ссылок: Future и Promise каждый сам Ref/Destroy; ссылки, отданные на хранение в структуру, поручаются структуре (дисциплина главы 5).

Трёхсторонняя связь отмены

Токен Cancel доступен и со стороны Promise, и со стороны Future (xrtPromiseCancelToken/xrtFutureCancelToken) — дерево отмены проходит через пару насквозь; xrtFutureCancel — прямая кнопка. Форма связи: родительский токен (дерево главы 54) → параметр pParentCancel при создании — отмена родителя даёт Future финал Cancelled (пересылать вручную не нужно); Cancel конца чтения обратно уведомляет конец записи (оператор прерывает работу в 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

Что только что произошло. (1) xrtPromiseCreate(&pFuture, NULL) — одним вызовом оба конца (NULL — без родительского токена; повесьте — и получите древовидную связь главы 54). (2) Конец Promise публикует значение 42 — одноразовый финал Resolved. (3) Конец Future ждёт Wait, xrtFutureValue читает заимствованием (срок действия до следующей операции — чтобы держать, сами Ref/копируйте). (4) Печатается 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

Что только что произошло. (1) Обратный вызов продолжения addFive(源结果, 输出Promise, 数据) (исходный результат, выходной Promise, данные) — читает значение исходного Future, прибавляет пять, публикует через выходной Promise: вход и выход оба асинхронны, и само преобразование — звено асинхронной цепи. (2) Ожидание в хвосте даёт result: 105 (100→+5) — у главной линии нет ни вложенности, ни промежуточных ожиданий, одна прямая до финального значения. (3) Провал распространяется: если источник Rejected, продолжение по конвенции не публикует (или переправляет ошибку) — ошибка идёт по цепи до хвоста (при написании продолжений отказ источника обрабатывается явно — в зоне ловушек есть экземпляр). (4) Образец future_combine — редакция комбинатора: из нескольких источников winner[1] (22 по индексу 1) прибывает первым — побеждает завершившийся первым, индекс и значение вместе.

Полная программа: 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

Что только что произошло. (1) Create за один шаг даёт Future и мост; xrtFutureBridgePromise одалживает конец записи — владение остаётся у вызывающего, обязанность уничтожения не меняется. (2) Watch (навесить переправку отмены) → Ready (открыть шлюз) → Promise пишет терминальное состояние → Future читает значение — порядок нарочно поставлен «сборка вперёд»; в настоящей асинхронности эти три шага могут чередоваться как угодно, мост переваривает всё. (3) Второй мост показывает путь неудачи: Init на собственный Promise, публикация Fail — после этого Wait возвращает ложь, а обратные вызовы завершения только возвраща́ют, никогда не пишут. Обратный вызов отмены (exampleCancel) заодно показывает факт переправки до того, как Unwatch к нему присоединится.

58.4契约

  • Одноразовость: финал ровно один, неизменяем; повторная публикация отвергается.
  • Три финала: Resolved/Rejected/Cancelled — единообразно в xfutureresult; отмена ≠ отказ (глава 54).
  • Контракт моста: терминальные записи приостановлены до Ready, после Fail — только возврат; мост заимствует Promise, не владея; Unwatch присоединяется к обратному вызову отмены, прежде чем мост можно оставить.
  • Заимствованное чтение: Value/Error заимствованы — употребить немедленно или держать свою копию; использовать до Destroy.
  • Дисциплина продолжений: явно обрабатывать три состояния источника (переправить отказ или деградировать — не глотать и не падать); выход обязан публиковаться (иначе повиснет нижестоящий).
  • Балансировка ссылок: оба конца сами Ref/Destroy; ссылки, хранимые в структурах, ведаются структурой (дисциплина владения главы 5).
  • Связь отмены: родительский токен при создании — автоматическая связь; оба конца дают CancelToken; оператор прерывает работу в Watch токена.

Типичные ошибки

Ловушка 1: продолжение глотает отказ источника:Ловушка 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);   /* Reject или Cancel по State источника - цепь не рвётся */
	}
}
Ловушка 2: Value читает старое значение или висячее:Ловушка 2: Value читает старое значение или висячее

Симптом: читается прежнее значение или мусор до завершения; изредка крах (значение указывает в освобождённую память).

Причина: чтение до финала (одноразовый результат не готов) или хранение заимствованного значения дальше Destroy/следующей операции Future.

错误写法
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 и значение/ошибка конца чтения по пунктам).

Продвинутое: четырёхзвенный конвейер

Кеш → при промахе база → преобразование → отложенная запись: четыре звена продолжений (на каждом звене имитация асинхронной задержки) — в хвосте ожидание финального значения; в среднее звено внедрить отказ и проверить распространение до хвоста.

Вызов: многорепликовый читатель

Три Future-«реплики» (каждая со своей имитацией задержки) + комбинатор 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 записей)

Полные сигнатуры, ограничения параметров, возвращаемые значения и ошибки