【问题标题】:How can I transfer some values ​into a Rust generator at each step?如何在每一步将一些值转移到 Rust 生成器中?
【发布时间】:2019-06-05 12:09:04
【问题描述】:

我将生成器用作长期异步线程(请参阅 How to implement a lightweight long-lived thread based on a generator or asynchronous function in Rust?) 在用户交互场景中。我需要在每一步将用户输入传递给生成器。我想我可以用RefCell 做到这一点,但不清楚如何在创建实例时将引用转移到生成器内部的RefCell

fn user_scenario() -> impl Generator<Yield = String, Return = String> {
    || {
        yield format!("what is your name?");
        yield format!("{}, how are you feeling?", "anon");
        return format!("{}, bye !", "anon");
    }
}

UserData 结构包含用户输入,第二个结构包含由UserData 和生成器实例组成的用户会话。会话收集在HashMap

struct UserData {
    sid: String,
    msg_in: String,
    msg_out: String,
}

struct UserSession {
    udata_cell: RefCell<UserData>,
    scenario: Pin<Box<dyn Generator<Yield = String, Return = String>>>,
}

type UserSessions = HashMap<String, UserSession>;

let mut sessions: UserSessions = HashMap::new();

UserData 是在接收用户输入时创建的——此时我需要在生成器中发送一个指向UserData 的链接,将其包装在RefCell 中,但我不知道该怎么做因为生成器的生命周期为 'static,而 RefCell 的生命周期较短!

let mut udata: UserData = read_udata(&mut stream);
let mut session: UserSession;

if udata.sid == "" { //new session
    let sid = rnd.gen::<u64>().to_string();
    udata.sid = sid.clone();
    sessions.insert(
        sid.clone(),
        UserSession {
            udata_cell: RefCell::new(udata),
            scenario: Box::pin(user_scenario())
        }
    );
    session = sessions.get_mut(&sid).unwrap();
} 

full code is here,但是这里的生成器看不到用户输入。

【问题讨论】:

  • 如果你试图在线程之间访问数据,你应该使用某种形式的Mutex而不是RefCell
  • @TheIncorrigible1,谢谢,但当前程序是单线程的,据我了解的问题是生成器有一个“静态生命周期”,而我的 RefCell(...) 生命周期较短,我仍然不明白如何同步生命周期。
  • long-lived asynchronous threads 那你能澄清一下吗?
  • 也许说得不好,我的意思是仅在生成器或异步/等待上的 gorutin 类似物。 Gorutin 栈下最多占用 8Kb,比较敏感,异步比较经济。问题在于,在 Rust 中,这些都是夜间构建和薄弱的文档。

标签: rust generator


【解决方案1】:

免责声明:恢复参数是生成器的计划扩展,因此在将来的某个时候可以使用&amp;UserData 恢复参数。


目前,我建议共享所有权。成本相当小(一次内存分配,一次间接),并且会为您省去很多麻烦:

struct UserSession {
    user_data: Rc<RefCell<UserData>>,
    scenario: ..,
}

构建于:

let user_data = Rc::new(RefCell::new(udata));
UserSession {
    user_data: user_data.clone(),
    scenario: Box::pin(user_scenario(user_data))
}

然后,会话和生成器都可以轮流访问UserData,一切都很好。

有一个小问题:小心范围。如果您在yield 点上保持.borrow() 处于活动状态,这是可能的,那么当您尝试在生成器之外对其进行写入时,您将遇到运行时错误。


更复杂的解决方案是使用消息队列;这也将涉及内存分配等...我认为您的UserData 结构是一对队列的退化形式:它是两个具有一条消息容量的队列。您可以使用常规队列使其更明确,但这不会给您带来太多收益。

【讨论】:

  • 计划中的生成器扩展——你能为此链接一些东西以便我们阅读吗?
  • 如何从生成器代码中获取数据的链接,因为在 Rust 中没有全局变量或单例。唯一的方法是在初始化生成器时传递单元格引用:Box::pin(user_scenario(&amp;some_ref)) 但是编译器禁止这样做,因为生成器的生命周期是“静态的”,并且任何变量的生命周期都较短(在我们的例子中,直到我们从HashMap)。
  • @epishman:不要传递链接,传递Rc 的克隆 :) 我更新了答案以显示如何。
  • @Matthieu M. 非常感谢您对borrow() 的说明,没有您我会被困几天:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-01-26
  • 2014-07-05
  • 2014-07-30
  • 1970-01-01
相关资源
最近更新 更多