欢迎!您可能不喜欢使用 C++ 编写描述多步骤的代码 异步操作
fpromise::promise<>
[1]
可以更轻松地做到这一点。本指南介绍了异步
控制流编程,并提供了解决这些难题的常见使用模式
fpromise::promise<>
库中的问题。
是什么让异步代码变得具有挑战性?
在 fpromise::promise<>
库中,异步任务是指
由多个具有显式挂起功能的同步代码块组成
积分。
定义异步任务时,必须针对以下事项提供解决方案 问题:
表达控制流:同步的顺序是什么 以及数据块之间的流动方式,如何在 如何理解?
状态管理和资源:需要什么中间状态才能执行 支持任务执行,必须获取哪些外部资源?怎么样 这些表达方式是什么?如何安全地进行处理?
术语
fpromise::promise<>
是由 lambda 或 用于描述最终生成 值或错误。- 处理程序函数是在创建 promise 时提供的回调。
- 接续函数是提供给 扩展。
fpromise::executor
负责调度和执行 promise。 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 在解析为 Success 或 出错了。
.then()
、.and_then()
、.or_else()
:链接异步块
通常,复杂的任务可以分解为更小的更精细的任务。每个 这些任务需要异步执行 依赖项,则需要保留这些任务。可以是 通过不同的组合来实现,例如:
fpromise::promise::then()
对于定义任务依赖项非常有用,因为 无论任务 1 的状态如何,都依次执行任务 1 和任务 2。前一个任务的 结果是通过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()
...
}
});
fpromise::promise::and_then()
仅可用于定义任务依赖项 如果任务 1 成功,则触发该消息。系统会通过 类型为ValueType&
或ValueType&
的参数。
auto execute_task_1_then_task_2 =
fpromise::make_promise([]() { ... }).and_then([](ValueType& success_value) {
...
});
fpromise::promise::or_else()
只有在定义任务依赖项时才能派上用场 任务 1 失败的情况。系统会通过一个 类型为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());
});
}
fpromise::join_promise_vector()
在 promise 存储在
std::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()
调用)。
不要返回 fpromise::result<...>
(使用 fpromise::ok
或 fpromise::error
),
处理程序函数可能会返回一个新的 promise,该 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
:违反承诺以避免内存安全违规行为
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 完成 使用同一排序器对象封装的最后一个 promise
#include <lib/fpromise/sequencer.h>
// TODO
fpromise::bridge
:与基于回调的异步函数集成
TODO:fpromise::bridge 对于将连续与基于回调的异步连接起来非常有用 函数
#include <lib/fpromise/bridge.h>
// TODO
fpromise::bridge
:分离单个接续链的执行
TODO:fpromise::bridge 对于将一个承接链分离为两个也很有用
promise 可在不同的 fpromise::executor
实例上执行
常见问题
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
- 常见误区: 捕获状态生命周期