Ориентация главы
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概念
Пара ролей и трёхфинальная развязка
Одноразовость — идентичность 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:
#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; }
Что только что произошло. (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-стиль, два звена:
#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; }
Что только что произошло. (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:
*/ #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; }
Что только что произошло. (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 токена.
Типичные ошибки
Симптом: хвост цепи вечно не получает результата (повисание) или получает нулевое значение как успех — отказ верхнего потока молча выброшен продолжением.
Причина: обратный вызов продолжения написан только под успех — 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 источника - цепь не рвётся */ } }
Симптом: читается прежнее значение или мусор до завершения; изредка крах (значение указывает в освобождённую память).
Причина: чтение до финала (одноразовый результат не готов) или хранение заимствованного значения дальше 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 записей)
Полные сигнатуры, ограничения параметров, возвращаемые значения и ошибки