#concurrency #run-time #task #scope #structured #cancellation #extension

task_scope

支持结构化并发和任务创建的异步运行时扩展

2个版本

0.1.1 2020年1月9日
0.1.0 2020年1月6日

#1915 in 异步

MIT/Apache

39KB
699

task_scope

task_scope 是一个异步运行时扩展,用于向现有运行时添加对结构化并发的支持。

更多详情,请参阅 API文档

许可

许可如下之一

您可选其中之一。

贡献

除非您明确表示,否则根据Apache-2.0许可证定义,您提交的任何贡献均应如上双许可,无需任何额外条款或条件。


lib.rs:

为现有运行时添加结构化并发支持的运行时扩展。

什么是结构化并发?

结构化并发 是一种编程范式,它允许异步操作仅在特定范围内运行,从而形成一个类似于常规函数调用栈的操作堆栈。由于父操作在所有子操作完成之前等待,结构化并发有助于并发程序的本地区分。

任务外并发被认为是有害的

大多数异步程序由 async 函数和如 selectjoin 之类的任务内并发原语组成,这使得这些future自动具有良好的结构。由于“future在被轮询之前什么也不做”,内部(子)操作的执行非常明确(通常在 await 点)。此外,取消future是通过丢弃它来完成的,这将回收操作使用的资源,包括内部future。这个丢弃链将取消传播到操作堆栈的底部。

任务外并发,例如 spawn,然而,会破坏结构。它们允许我们启动一个新的执行单元,该单元可以逃离父栈。尽管框架提供了一种方法来连接派生任务,但它们没有正确地传播取消操作。如果你取消派生任务,派生任务可能会无限期地存活于父任务。

task_scope 设计了一个函数 spawn,它正确地尊重了取消操作。 scope 函数定义了一个生命周期,在此生命周期内,内部任务可以运行。如果你发出优雅的取消或取消作用域,运行时将向所有子任务发送取消信号。

取消点

task_scope 需要任务定期通过取消点才能有效工作。考虑以下(假设的)示例

use tokio::io::*;

let mut read = repeat(42); // very fast input
let mut write = sink(); // very fast output

copy(&mut read, &mut write).await.unwrap();

这个程序几乎进入了一个无限循环,因为 readwrite 永远不会终止。更糟糕的是,由于 I/O 操作总是成功,并且 copy 函数试图尽可能继续执行,因此无法从外部取消这个循环。因此,派生任务必须合作地检查取消或在循环中定期让出执行权,以保持良好的结构。

task_scope 提供了一个方便的函数 cancelable 来自动处理取消。它包装给定的 Future/AsyncRead/AsyncWrite,并在进行内部计算之前检查取消(优雅或强制)。上面的示例将看起来像

use futures::pin_mut;
use tokio::io::*;

use task_scope::cancelable;

let read = cancelable(repeat(42)); // very fast, but cancelable input
pin_mut!(read); // needed for Unpin bound of copy
let mut write = sink(); // very fast output

// this will terminate with an error on cancellation
copy(&mut read, &mut write).await.unwrap();

如果取消逻辑更复杂,您可以手动轮询 Cancellation 以检查取消信号。

宽限期和慈悲期

task_scope 支持两种取消模式:优雅取消和强制取消。

您可以通过调用作用域的 cancel 方法来启动优雅取消。这会通知作用域内的任务,并给予它们“宽限期”以开始取消。作用域像往常一样等待所有任务完成。

当作用域被丢弃或调用 force_cancel 时,强制取消会传播到任务。鉴于取消长时间运行的任务确实很困难,如上所示,已取消的任务会得到一个“慈悲期”。任务可以继续执行,直到它们在下一次让出执行权时,然后运行时会自动取消任务。任务应尽可能缩短慈悲期,因为这在技术上破坏了程序的并发结构(一个子任务存活于已丢弃的父任务)。

task_scope 是(几乎)与执行器无关的

task_scope 通过向运行时(如 async-std 和 Tokio)提供的当前异步上下文中添加取消信息来工作。因此,取消的传播不受执行器的影响。甚至可以将一个运行时中运行的任务的取消传播到另一个运行时中运行的子任务。

然而,task_scope 并没有提供自己的 spawn API,而是将创建任务委托给执行器。因此,要使用 spawn,你需要通过启用 task_scope 的功能来声明你想要通过 task_scope 扩展的运行时。例如,如果你想在 Tokio 运行时创建一个可取消的任务,你需要启用 task_scope"tokio" 功能,并调用 task_scope::spawn::tokio::spawn。目前支持 async-std 和 Tokio。

如果只启用了一个运行时,task_scope::spawn 指的是该运行时的 spawn 函数。默认情况下,只启用了 "tokio" 功能。

依赖

~6–17MB
~201K SLoC