中断

有时候, 你可能需要处理一些交互式的操作, 像是等待一个 FileUploadSuccessfully 的事件来获取其内容, 这时候你可以考虑使用我们的 中断(Interrupt) 特性.

首先我们需要一个 InterruptControl 实例, 用于生成内部使用的一些东西以及接受并传回结果.

from graia.broadcast.interrupt import InterruptControl

inc = InterruptControl(broadcast)

让我们假设, 客户端需要先将当前的 Session 的模式转换为上传图片, 服务端在收到请求后指示客户端上传图片文件数据.
一般来讲是这样写的:

@bcc.receiver(WebsocketDataReceived, decorators=[
    Endpoint("set_mode_upload")
])
async def set_mode_upload(session: Session = getCurrentSession()):
    session.stats['mode'] = 'upload'

@bcc.receiver(WebsocketDataReceived, decorators=[
    DataType(bytes)
])
async def upload(session: Session = getCurrentSession()):
    if session.stats['mode'] == 'upload':
        ...

那, 有没有更加简单的办法? Interrupt 就是拿来消灭这种 "勤劳" 的设计的, 毕竟懒惰是工程师的美德.

@bcc.receiver(WebsocketDataReceived, decorators=[
    Endpoint("upload")
])
async def upload(session: Session = getCurrentSession()):
    @Waiter.create_using_function([WebsocketDataReceived], decorators=[DataType(bytes)])
    def waiter(received: bytes = Received.toBytes(), sub_session: Session = getCurrentSession()):
        if sub_session == session:
            return received

    received_resource = await inc.wait(waiter)
    resource = await database.save(Resource.create_from_bytes(received_resource))
    return {
        "id": resource.id,
        # ...
    }

很好, 这段代码虽然比上面那段要长一点点, 但维护起来应该是更容易的, 并且也方便加上各种校验什么的.

在这段代码中, 我们使用 Interrupt, 从当前正在运行的流程切出, 并等待客户端上传数据, 也就是 "交互".

而差不多有一年了吧? 无数的实例都在说明 Interrupt 在处理 交互式 的流程处理时, 有着很大优势, 这差不多就像 JavaScript 中将 Promise(...).then(...) 的回调地狱换成 async-await 一样.

或者, 更进一步, 我们造了一个多用户/多房间的游戏, 我们便可以直接将一个房间/频道/作战地图/会话直接通过单个的处理器进行处理, 而不用去设立繁杂的状态和判断, 这还可以用于各种各样需要频繁交互的, 比如说

  • 使用了多 BOT 账号的 Arcaea 的查分器, 配合一个资源池去简单的处理;
  • 需要填的选项太多而需要不断问询用户的应用;
  • 服务端处理用户, 抑或反过来, 用于双方并行处理并共享各种各样的消息/情报等.

同时, 因为 Interrupt 使用了经过高层封装的 Executor, 所以 Dispatcher, Decorator 等等特性也可以一并使用, 你就简单的当个可以向上层返回结果的事件接受器, 这样理解也是没问题的.

这个特性的应用相当广泛, 限制它可能性的也许只有想象力吧.

Tip

在 Graia Ariadne 的文档中也有对于 Interrupt 这一特性的阐述, 另见此处