热烈欢迎!您可能不喜欢使用 C++ 编写描述多步异步操作的代码。
fpromise::promise<>
[1]
可让此过程变得更轻松。本指南介绍了异步控制流编程中的常见问题,并提供了 fpromise::promise<> 库中可解决这些问题的常用使用模式。
异步代码的难点是什么?
在 fpromise::promise<> 库中,异步任务是指由多个具有显式挂起点的同步代码块组成的任务。
定义异步任务时,必须针对以下问题提供解决方案:
表达控制流:如何表达同步块的序列以及它们之间的数据流?如何以易于理解的方式实现这一点?
状态和资源管理:支持任务执行需要哪些中间状态,以及必须捕获哪些外部资源?如何表达这种需求?如何安全地实现这种需求?
术语
fpromise::promise<>是一个仅可移动的对象,由一组 lambda 或回调组成,用于描述最终会生成值或错误的一项异步任务。- 处理函数是在创建 promise 时提供的回调。
- 延续函数是为现有 promise 上的各种延续方法提供的回调。
fpromise::executor负责调度和执行 promise。只有在承诺的所有权转移到fpromise::executor后,承诺才会运行。此时,执行器负责安排和执行任务。fpromise::context可选择性地传递给处理程序和延续函数,以获取对fpromise::executor以及低级挂起和恢复控件的访问权限。
构建和执行您的第一个 fpromise::promise<>
我们来编写一个简单的 promise。
#include <lib/fpromise/promise.h>
...
fpromise::promise<> p = fpromise::make_promise([] {
// This is a handler function.
auto world_is_flat = AssessIfWorldIsFlat();
if (world_is_flat) {
return fpromise::error();
}
return fpromise::ok();
});
p 现在包含一个描述简单任务的 promise。
为了运行 promise,必须在 fpromise::executor 的实现上调度它。最常用的执行器是 async::Executor [2],它会在 async_dispatcher_t 上安排回调。为了进行测试和探索,我们还使用了 fpromise::single_threaded_executor 及其关联方法 fpromise::run_single_threaded() [3]。
// When a promise is scheduled, the `fpromise::executor` takes ownership of it.
fpromise::result<> result = fpromise::run_single_threaded(std::move(p));
assert(result.is_ok());
构建更复杂的 fpromise::promise<>
退回、错误类型和解决状态
如上所述,fpromise::promise<> 的模板实参表示返回类型和错误类型:
fpromise::promise<ValueType, ErrorType>
可以省略错误类型,系统会采用默认错误类型 void(例如,fpromise::promise<MyValueType> 等同于 fpromise::promise<MyValueType,
void>)。
在执行期间,Promise 最终必须达到以下状态之一:
- 成功:处理函数或最后一个延续函数(见下文)已返回
fpromise::ok()。 - 错误:处理函数或某个延续函数已返回
fpromise::error(),但没有后续延续函数拦截该返回值。 - 已放弃:Promise 在解析为成功或错误之前被销毁。
.then()、.and_then()、.or_else():链接异步块
通常,复杂的任务可以分解为更细化的较小任务。这些任务都需要异步执行,但如果任务之间存在某种依赖关系,则需要保留这些依赖关系。这可以通过不同的组合器来实现,例如:
fpromise::promise::then()可用于定义任务依赖项,即先执行任务 1,然后执行任务 2,无论任务 1 的状态如何。先验任务的结果通过fpromise::result<ValueType, ErrorType>&或const fpromise::result<ValueType, ErrorType>&类型的实参接收。
auto execute_task_1_then_task_2 =
fpromise::make_promise([]() -> fpromise::result<ValueType, ErrorType> {
...
}).then([](fpromise::result<ValueType, ErrorType>& result) {
if (result.is_ok()) {
...
} else { // result.is_error()
...
}
});
- 只有在任务 1 成功的情况下,
fpromise::promise::and_then()才能用于定义任务依赖关系。通过ValueType&或ValueType&类型的实参接收上一个任务的结果。
auto execute_task_1_then_task_2 =
fpromise::make_promise([]() { ... }).and_then([](ValueType& success_value) {
...
});
- 只有在任务 1 失败的情况下,
fpromise::promise::or_else()才能用于定义任务依赖项。通过ErrorType&或const ErrorType&类型的实参接收先前任务的结果。
auto execute_task_1_then_task_2 =
fpromise::make_promise([]() { ... }).or_else([](ErrorType& failure_value) {
...
});
fpromise::join_promises()和fpromise::join_promise_vector():并行执行
有时,可以执行多个彼此之间没有依赖关系的 promise,但汇总结果是下一个异步步骤的依赖关系。在这种情况下,fpromise::join_promises() 和 fpromise::join_promise_vector() 用于联接多个 Promise 的结果。
当每个 promise 都被变量引用时,使用 fpromise::join_promises()。
fpromise::join_promises() 支持异构 promise 类型。通过 std::tuple<...>& 或 const
std::tuple<...>& 类型的实参接收之前任务的结果。
auto DoImportantThingsInParallel() {
auto promise1 = FetchStringFromDbAsync("foo");
auto promise2 = InitializeFrobinatorAsync();
return fpromise::join_promises(std::move(promise1), std::move(promise2))
.and_then([](std::tuple<fpromise::result<std::string>,
fpromise::result<Frobinator>>& results) {
return fpromise::ok(std::get<0>(results).value() +
std::get<1>(results).value().GetFrobinatorSummary());
});
}
当 promise 存储在 std::vector<> 中时,使用 fpromise::join_promise_vector()。此外,还必须满足所有 promise 都是同类(类型相同)这一限制。通过 std::vector<fpromise::result<ValueType, ErrorType>>& 或 const std::vector<fpromise::result<ValueType, ErrorType>>& 类型的实参接收先前任务的结果。
auto ConcatenateImportantThingsDoneInParallel() {
std::vector<fpromise::promise<std::string>> promises;
promises.push_back(FetchStringFromDbAsync("foo"));
promises.push_back(FetchStringFromDbAsync("bar"));
return fpromise::join_promise_vector(std::move(promises))
.and_then([](std::vector<fpromise::result<std::string>>& results) {
return fpromise::ok(results[0].value() + "," + results[1].value());
});
}
return fpromise::make_promise():通过返回新的 promise 进行链式调用或分支
在运行时再决定要链接哪些 promise 可能会很有用。此方法与以语法方式(通过使用连续的 .then()、.and_then() 和 .or_else() 调用)执行的链式调用形成对比。
处理函数可以返回一个新 promise(而不是返回 fpromise::result<...> [使用 fpromise::ok 或 fpromise::error]),该 promise 将在处理函数返回后进行评估。
fpromise::make_promise(...)
.then([] (fpromise::result<>& result) {
if (result.is_ok()) {
return fpromise::make_promise(...); // Do work in success case.
} else {
return fpromise::make_promise(...); // Error case.
}
});
此模式还可用于将可能很长的 Promise 分解为更小的可读块,例如通过让延续函数返回上述示例中 DoImportantThingsInParallel() 的结果。
声明并保持中间状态有效
某些任务要求仅在 promise 本身处于待处理或执行状态时保持状态有效。此状态不适合移入任何给定的 lambda,因为它需要共享;也不适合将所有权转移到生命周期更长的容器,因为我们希望其生命周期与 promise 相关联。
虽然不是唯一的解决方案,但同时使用 std::unique_ptr<> 和 std::shared_ptr<> 是一种常见模式:
std::unique_ptr<>
fpromise::promise<> MakePromise() {
struct State {
int i;
};
// Create a single std::unique_ptr<> container for an instance of State and
// capture raw pointers to the state in the handler and continuations.
//
// Ownership of the underlying memory is transferred to a lambda passed to
// `.inspect()`. |state| will die when the returned promise is resolved or is
// abandoned.
auto state = std::make_unique<State>();
state->i = 0;
return fpromise::make_promise([state = state.get()] { state->i++; })
.and_then([state = state.get()] { state->i--; })
.inspect([state = std::move(state)](const fpromise::result<>&) {});
}
std::shared_ptr<>
fpromise::promise<> MakePromise() {
struct State {
int i;
};
// Rely on shared_ptr's reference counting to destroy |state| when it is safe
// to do so.
auto state = std::make_shared<State>();
state->i = 0;
return fpromise::make_promise([state] { state->i++; }).and_then([state] {
state->i--;
});
}
fpromise::scope:放弃 promise 以避免内存安全违规
fpromise::scope 可用于将 fpromise::promise<> 的生命周期与内存中的资源相关联。例如:
#include <lib/fpromise/scope.h>
class A {
public:
fpromise::promise<> MakePromise() {
// Capturing |this| is dangerous: the returned promise will be scheduled
// and executed in an unknown context. Use |scope_| to protect against
// possible memory safety violations.
//
// The call to `.wrap_with(scope_)` abandons the promise if |scope_| is
// destroyed. Since |scope_| and |this| share the same lifecycle, it is safe
// to capture |this|.
return fpromise::make_promise([this] {
// |foo_| is critical to the operation!
return fpromise::ok(foo_.Frobinate());
})
.wrap_with(scope_);
}
private:
Frobinator foo_;
fpromise::scope scope_;
};
void main() {
auto a = std::make_unique<A>();
auto promise = a->MakePromise();
a.reset();
// |promise| will not run any more, even if scheduled, protected access to the
// out-of-scope resources.
}
fpromise::sequencer:阻塞对单独 promise 完成的 promise
TODO:您可以 .wrap_with(sequencer) 来阻止此 promise,直到以同一 sequencer 对象封装的最后一个 promise 完成为止
#include <lib/fpromise/sequencer.h>
// TODO
fpromise::bridge:与基于回调的异步函数集成
TODO:fpromise::bridge 可用于从基于回调的异步函数链接延续
#include <lib/fpromise/bridge.h>
// TODO
fpromise::bridge:分离单个延续链的执行
TODO:fpromise::bridge 也可用于将一个延续链解耦为两个可在不同 fpromise::executor 实例上执行的 promise
常见陷阱
and_then 或 or_else 的序列必须具有兼容的类型
使用 and_then 构建 promise 时,每个后续延续可能具有不同的 ValueType,但必须具有相同的 ErrorType,因为 and_then 会转发之前的错误而不消耗它们。
使用 or_else 构建 promise 时,每个后续延续可能具有不同的 ErrorType,但必须具有相同的 ValueType,因为 or_else 会转发之前的值,而不会使用它们。
如需在序列中间更改类型,请使用 then 来使用之前的结果并生成所需类型的新结果。
以下示例无法编译,因为最后一个 and_then 处理程序返回的错误类型与前一个处理程序的结果不兼容。
auto a = fpromise::make_promise([] {
// returns fpromise::result<int, void>
return fpromise::ok(4);
}).and_then([] (const int& value) {
// returns fpromise::result<float, void>
return fpromise::ok(value * 2.2f);
}).and_then([] (const float& value) {
// ERROR! Prior result had "void" error type but this handler returns const
// char*.
if (value >= 0)
return fpromise::ok(value);
return fpromise::error("bad value");
}
使用 then 使用结果并更改其类型:
auto a = fpromise::make_promise([] {
// returns fpromise::result<int, void>
return fpromise::ok(4);
}).and_then([] (const int& value) {
// returns fpromise::result<float, void>
return fpromise::ok(value * 2.2f);
}).then([] (const fpromise::result<float>& result) -> fpromise::result<float, const char*> {
if (result.is_ok() && result.value() >= 0)
return fpromise::ok(value);
return fpromise::error("bad value");
}
处理程序 / 延续函数可以返回 fpromise::result<> 或新的 fpromise::promise<>,但不能同时返回两者
您可能希望编写一个处理程序,该处理程序可以在一个条件分支中返回 fpromise::promise<>,而在另一个条件分支中返回 fpromise::ok() 或 fpromise::error()。这是非法的,因为编译器无法将 fpromise::result<> 强制转换为 fpromise::promise<>。
解决方法是返回一个解析为所需结果的 fpromise::promise<>:
auto a = fpromise::make_promise([] {
if (condition) {
return MakeComplexPromise();
}
return fpromise::make_ok_promise(42);
});
延续签名
您是否看到过类似这样的错误消息?
../../sdk/lib/fit-promise/include/lib/fpromise/promise_internal.h:342:5: error: static_assert failed "The provided handler's last argument was expected to be of type V& or const V& where V is the prior result's value type and E is the prior result's error type. Please refer to the combinator's documentation for
a list of supported handler function signatures."
或:
../../sdk/lib/fit-promise/include/lib/fpromise/promise.h:288:5: error: static_assert failed due to requirement '::fpromise::internal::is_continuation<fpromise::internal::and_then_continuation<fpromise::promise_impl<fit::function_impl<16, false, fpromise::result<fuchsia::modular::storymodel::StoryModel, void> (fpromise::context &)> >, (lambda at ../../src/modular/bin/sessionmgr/story/model/ledger_story_model_storage.cc:222:17)>, void>::value' "Continuation type is invalid. A continuation is a callable object with this signature: fpromise::result<V, E>(fpromise::context&)."
这很可能意味着某个延续函数的签名无效。不同延续函数的有效签名如下所示:
对于 .then():
.then([] (fpromise::result<V, E>& result) {});
.then([] (const fpromise::result<V, E>& result) {});
.then([] (fpromise::context& c, fpromise::result<V, E>& result) {});
.then([] (fpromise::context& c, const fpromise::result<V, E>& result) {});
对于 .and_then():
.and_then([] (V& success_value) {});
.and_then([] (const V& success_value) {});
.and_then([] (fpromise::context& c, V& success_value) {});
.and_then([] (fpromise::context& c, const V& success_value) {});
对于 .or_else():
.or_else([] (E& error_value) {});
.or_else([] (const E& error_value) {});
.or_else([] (fpromise::context& c, E& error_value) {});
.or_else([] (fpromise::context& c, const E& error_value) {});
对于 .inspect():
.inspect([] (fpromise::result<V, E>& result) {});
.inspect([] (const fpromise::result<V, E>& result) {});
捕获和实参生命周期
Promise 由处理程序和延续函数(通常是 lambda)组成。在构建 lambda 捕获列表时,必须谨慎操作,以免捕获在相关处理程序或延续执行时无效的内存。
例如,此 promise 会捕获在 Foo() 返回时(以及在返回的 promise 被调度和执行时)保证无效的内存。
fpromise::promise<> Foo() {
int i;
return fpromise::make_promise([&i] {
i++; // |i| is only valid within the scope of Foo().
});
}
实际代码中的实例更加细致。一个不太明显的示例:
fpromise::promise<> Foo() {
return fpromise::make_promise(
[i = 0] { return fpromise::make_promise([&i] { i++; }); });
}
fpromise::promise 会急切地销毁处理程序和继续函数:最外层的处理程序会在返回最内层的处理程序后立即被销毁。如需了解在这种情况下应使用的正确模式,请参阅上文中的“声明并保持中间状态有效”。
>>> 部分来撰写
- 从一种错误类型转换为另一种错误类型
- fpromise::bridge
- 常见陷阱:捕获的状态生命周期