首页 > 解决方案 > 为什么在等待点上持有非发送类型会导致非发送未来?

问题描述

Sendtrait的文档中,有一个很好的例子说明 Rc is not 之类的东西Send,因为在两个不同的线程中克隆/删除会导致引用计数不同步。

然而,不太清楚的是,为什么在 an 中的一个点上持有与非Send类型的绑定会导致生成的未来也是非类型。当编译器在异步手册的变通方法一章中过于保守时,我能够找到一种变通方法,但它并没有回答我在这里提出的问题。awaitasync fnSend

也许有人可以举例说明为什么在 a 中有一个非Send类型是Future可以的,但在 anawait中保持它不是?

标签: asynchronousrustfuture

解决方案


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

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

Future not to be完全没问题Send,只是意味着你只能在单线程运行时执行它。


也许有人可以举例说明为什么在 a 中有一个非Send类型是Future可以的,但在 anawait中保持它不是?

考虑以下简单示例:

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都可能来自不同的线程,在这种情况下,运行时会在调用它之前将其移动到另一个线程。这不起作用,因为无法在线程之间发送。AddVotespollRc

但是,如果未来只是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.


推荐阅读