在Poise(Serenity) Discord机器人中获取Context及rx使用疑问
问题
我想获取Poise框架中的Context对象,并将其传递给不同函数,尤其是要在send_changes_via_bot函数里用ctx.say()向Discord API发送更新。另外我对rx的使用有疑问:如果递归调用函数,由于main里的tx一直在发送数据,每次传入的rx会不会不一样?还是说旧的rx会被复制,因为rx不是指针形式?
代码示例
pub async fn bot(rx: &mpsc::Receiver<Value>) { let discord_token = fs::read_to_string("discordtoken.txt") .expect("Issue with token"); let framework = poise::Framework::builder() .options(poise::FrameworkOptions { // Macro takes care of ctx and user commands: vec![account_age(), set_gdrive_channel(), spawn_watcher()], ..Default::default() }) .token(discord_token) .intents(serenity::GatewayIntents::non_privileged()) .setup(|ctx, _ready, framework| { Box::pin(async move { poise::builtins::register_globally(ctx, &framework.options().commands) .await?; Ok(Data {}) }) }); // * ---------------------------------------------------------------- // * ---------------------------------------------------------------- // ! Don't know how to pass context here :( send_changes_via_bot(ctx, rx).await; // * ---------------------------------------------------------------- // * ---------------------------------------------------------------- framework.run().await.unwrap(); }
解答
一、获取并传递Poise的Context对象
你当前代码里的ctx只在setup闭包内部有效,没法直接在外部调用send_changes_via_bot时传入,这里给你两种可行的解决方式:
1. 把Context存入自定义Data结构体
- 先修改你的
Data结构体,添加字段存储serenity::Context(比Poise的Context更通用):use serenity::client::Context as SerenityContext; struct Data { discord_ctx: SerenityContext, } - 在
setup闭包里克隆并保存ctx:.setup(|ctx, _ready, framework| { Box::pin(async move { poise::builtins::register_globally(ctx, &framework.options().commands).await?; Ok(Data { discord_ctx: ctx.clone() }) }) }); - 后续要使用Context时,可通过
ctx.data()(在命令逻辑里)或者框架实例的data方法获取。如果要启动send_changes_via_bot,可以在on_ready钩子中拿到Data里的Context,再异步启动任务。
2. 利用on_ready钩子启动任务
直接在框架配置里添加on_ready钩子,等机器人初始化完成后,再启动send_changes_via_bot并传入Context:
.options(poise::FrameworkOptions { commands: vec![account_age(), set_gdrive_channel(), spawn_watcher()], on_ready: Some(|ctx, _ready| { Box::pin(async move { // 注意:mpsc::Receiver不能共享,这里需要处理所有权,比如转移或改用其他通道类型 let rx = rx.clone(); // 如果是broadcast类型的Receiver可克隆,mpsc则需要转移所有权 tokio::spawn(async move { send_changes_via_bot(ctx, rx).await; }); }) }), ..Default::default() })
注意:tokio::sync::mpsc::Receiver不支持克隆,同一时间只能有一个持有者接收消息。如果需要多消费者,建议改用broadcast或watch通道。
二、关于rx递归调用的疑问
- Rust的
mpsc::Receiver是所有权类型,不是指针。传递它时会转移所有权,旧的变量会立即失效,不会发生复制(因为mpsc::Receiver没有实现Clone,避免多消费者重复消费消息)。 - 递归调用时,每次传入的rx都是同一个接收流的所有权持有者——本质是把所有权从当前递归层级转移到下一层,不会出现多个rx同时接收消息的情况。
- 如果需要在多个地方接收消息,不要用
mpsc,改用tokio::sync::broadcast::Receiver,它支持克隆多个接收端,每个都能收到相同的消息。
内容的提问来源于stack exchange,提问作者ScreX
相关产品推荐
相关产品推荐

