为什么在等待点持有非发送类型会导致非发送未来?

Why does holding a non-Send type across an await point result in a non-Send Future?

Send trait 的文档中,有一个很好的例子说明 Rc 之类的东西不是 Send,因为两个不同线程中的 cloning/dropping 会导致引用计数失去同步。

然而,不太清楚的是,为什么在 async fn 中的 await 点持有对非 Send 类型的绑定会导致生成的未来也不是-Send。当编译器在异步手册的 work-arounds chapter 中过于保守时,我能够找到解决方法,但它并没有回答我在这里提出的问题。

也许有人可以举例说明为什么在 Future 中输入非 Send 是可以的,但将它放在 await 上却不行?

当您在异步函数中使用 .await 时,编译器会在后台构建一个状态机。每个 .await 引入一个新状态(当它等待某事时)并且其间的代码是状态转换(又名任务),它将根据某些外部事件(例如来自 IO 或计时器等)触发。

每个任务都被安排为由异步运行时执行,可以选择使用与前一个任务不同的线程。如果在线程之间发送状态转换不安全,那么生成的 Future 也不是 Send,因此如果您尝试在多线程运行时执行它,则会出现编译错误。

一个Future不是Send是完全可以的,这只是意味着你只能在单线程运行时执行它。


Perhaps someone could shed some light on this with an example of why having a non-Send type in a Future is ok, but holding it across an await is not?

考虑以下简单示例:

async fn add_votes(current: Rc<Cell<i32>>, post: Url) {
    let new_votes = get_votes(&post).await;
    *current += new_votes;
}

编译器将构造一个这样的状态机(简化):

enum AddVotes {
    Initial {
        current: Rc<Cell<i32>>,
        post: Url,
    },
    WaitingForGetVotes { 
        current: Rc<Cell<i32>>,
        fut: GetVotesFut,
    },
}
impl AddVotes {
    fn new(current: Rc<Cell<i32>>, post: Url) {
        AddVotes::Initial { current, post }
    }

    fn poll(&mut self) -> Poll {
        match self {
            AddVotes::Initial(state) => {
                let fut = get_votes(&state.post);
                *self = AddVotes::WaitingForGetVotes {
                     current: state.current,
                     fut
                }
                Poll::Pending
            }
            AddVotes::WaitingForGetVotes(state) => {
                if let Poll::Ready(votes) = state.fut.poll() {
                    *state.current += votes;
                    Poll::Ready(())
                } else {
                    Poll::Pending
                }
            }
        }
    }
}

在多线程运行时,对 poll 的每次调用都可能来自不同的线程,在这种情况下,运行时会 移动到 AddVotes调用 poll 之前的其他线程。这不会起作用,因为 Rc 不能在线程之间发送。

但是,如果未来只是在相同的状态转换中使用Rc,那就没问题了,例如如果 votes 只是一个 i32:

async fn add_votes(current: i32, post: Url) -> i32 {
    let new_votes = get_votes(&post).await;

    // use an Rc for some reason:
    let rc = Rc::new(1);
    println!("rc value: {:?}", rc);

    current + new_votes
}

在这种情况下,状态机将如下所示:

enum AddVotes {
    Initial {
        current: i32,
        post: Url,
    },
    WaitingForGetVotes { 
        current: i32,
        fut: GetVotesFut,
    },
}

Rc 未在状态机中捕获,因为它是在状态转换(任务)中创建和删除的,因此整个状态机(又名 Future)仍然是 Send.