babylon

[English]

cancellable

原理

标准coroutine机制中的co_await相当于std::future::get方法,即等待目标awaitable完成,并在之后才能恢复执行;但有时需要让等待具备提前结束的能力,最为典型的场景是timeout这类场景;

实现上Cancellable包装一个常规的awaitable,并通过插入一个proxy awaitable实现取消支持;一方面proxy awaitable传递co_await到包装的awaitable,并最终传导恢复执行动作到发起co_await的原始协程;另一方面,proxy awaitable并不将待恢复的协程句柄存在本地,而是存放到DepositBox中,并同时传输给注册的timer等执行取消动作的触发源;当包装的awaitable发起恢复,或者取消触发源发起取消时,会竞争DepositBox中存放的协程句柄,并由胜出方执行恢复动作;

用法示例

#include "babylon/coroutine/task.h"
#include "babylon/coroutine/cancellable.h"

using ::babylon::coroutine::Task;
using ::babylon::coroutine::Cancellable;

using Cancellation = typename Cancellable<A>::Cancellation;

Task<...> some_coroutine(...) {
  ...
  // 包装原始awaitable称为Cancellable
  auto optional_value = co_await Cancellable<A>(::std::move(a)).on_suspend(
    // 通过回调函数在协程挂起后接收相应的取消句柄
    [&](Cancellation cancel) {
      // 典型操作是把cancel句柄注册到某种timer机制,并在指定时间后调用cancel()发起取消
      // 从回调被执行开始,cancel就可用了,甚至在回调内部也可以直接发起cancel(),虽然一般这并没有什么意义
      on_timer(cancel, 100ms);
    }
  );
  // 如果a先完成,则返回非空值可供操作
  if (optional_value) {
    optional_value->item_member_function(...);
    some_function_use_item(*optional_value);
  } else {
    // 收到空值表示取消动作先一步发起
  } 
  ...
}