任务组合
在实际的工程项目中,各种任务通常以多种逻辑关系和时序关系组合在一起,而不是单纯的等待任务完成后再进行下一个任务, ASCO 提供了一些任务组合方式。
另外,除 select<N>
外,大多数组合函数都支持在同步上下文中调用。
非阻塞的串行任务组合 future::then()
future<T>::then()
用于将一个异步操作的结果传递给下一个异步操作,实现异步任务的串行组合。
它允许你在前一个 future
完成后,自动将结果传递给指定的异步函数,并返回一个新的 future
,表示整个组合任务的最终结果。
同时它返回另一个 futur
,可以继续对它进行任意的任务组合操作。
用法
future<int> f1 = some_async_func();
auto f2 = std::move(f1).then([](int result) -> future<std::string> {
// 可以使用 result 进行后续异步操作
co_return std::to_string(result);
});
then()
接收一个右值引用的 deduced this,通常在异步函数调用后直接调用 then()
即可,这里为了表示返回值类型,
特意先构造一个 future
对象,再将它移动。
传入的参数如果是 lambda 表达式,其参数不可以使用 auto
自动推导。
行为说明
then()
会等待当前 future 完成,将其结果作为参数传递给f
。then()
返回的 future 表示整个链式异步操作的最终结果。- 支持异常传递,若前一个 future 抛出异常,则异常会传递到 then 返回的 future。
注意事项
then()
只支持异步函数(返回 future)的链式组合。- 若在 future_inline 上调用 then,会自动将任务迁移到当前 worker 上执行。
异步的异常捕获 future::exceptionally()
future<T>::exceptionally()
用于为异步任务链提供异常处理能力。当前一个 future 抛出指定的异常时,
exceptionally
可以捕获并执行用户自定义的异常处理逻辑,如果抛出的不是所指定的异常,则继续重抛至 exceptionally
自己的 future。
用法
future<int> f = some_async_func();
auto f2 = std::move(f).exceptionally([](const std::runtime_error& e) {
std::cout << "caught: " << e.what() << std::endl;
});
exceptionally()
接收一个右值引用的 deduced this
,通常在异步函数调用后直接调用即可。
传入的参数可以是 lambda 表达式或其它可调用对象,参数类型需为异常类型或异常类型的引用或 std::exception_ptr
。
行为说明
exceptionally()
会等待当前 future 完成 。- 如果当前 future 正常返回,则
exceptionally
返回的 future 也正常返回原值。 - 如果当前 future 抛出异常,且异常类型与处理函数参数类型匹配,则调用处理函数,并返回
std::unexpected
包装的错误类型。 - 如果异常类型不匹配,则异常会继续向上传递。
- 返回类型为
future<std::expected<T, E>>
,其中E
由处理函数的返回类型自动推断。
注意事项
- 处理函数参数类型必须能匹配 future 抛出的异常类型,否则异常不会被捕获。
- 处理函数可以为 lambda、函数指针、std::function 等。
- 处理函数返回类型可以为 void,此时异常类型为 std::monostate,否则为实际返回类型。
- 若需捕获所有异常,可使用
std::exception
或std::exception_ptr
作为参数类型。
协程打断异常
当协程被打断(如通过 future.abort()
或 select<N>
选择逻辑打断其它分支)时,应抛出 coroutine_abort
异常。
你可以通过以下两种方式处理协程打断:
-
手动 try-catch 捕获
在协程体内用
try { ... } catch (const coroutine_abort&) { ... }
捕获打断异常,实现自定义清理或日志逻辑。 -
使用 future.aborted() 统一处理
通过
future<T>::aborted()
方法,可以为打断异常单独指定处理逻辑。例如:
auto f = some_async_func().aborted([] {
std::cout << "协程被打断" << std::endl;
});
当协程被打断时,aborted()
传入的处理函数会被调用,并返回 std::nullopt
,否则正常返回结果。
竞态任务组合(选择逻辑) select<N>
选择最先返回的协程继续运行,打断其它未返回或后返回的协程。
asco::interval in1s{1s};
asco::interval in500ms{500ms};
for (int i{0}; i < 6; i++) {
switch (co_await asco::select<2>{}) {
case 0: {
co_await in1s.tick();
std::cout << "1s\n";
break;
}
case 1: {
co_await in500ms.tick();
std::cout << "500ms\n";
break;
}
}
}
选择器将当前协程克隆出 N
个协程并同时唤醒运行。对构造的 select<N>
对象 co_await
后按协程被克隆的顺序返回 size_t
类型的值。
选择器仅对 select<N>
对象返回后的第一个异步任务有效。
最先返回的异步任务会将其它任务打断,因此即使后来的协程的已经返回,也会根据前文规定的可打断特性将其影响的状态恢复。
被打断的协程会将其调用者一并销毁;如果正确使用了 select<N>
,其调用者总是被克隆的 N 个协程中的 N-1 个。