You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何Mio的poll会对用户生成事件触发两次?

为什么Mio的Poll会重复触发用户事件?

这个问题我之前也踩过坑,核心原因和Mio的就绪状态管理逻辑直接相关——当你通过Registration设置就绪状态后,这个状态会一直保留,直到你主动清除它。如果不做清除操作,每次调用poll都会检测到这个“就绪”状态,自然会重复触发相同的事件。

结合你的代码场景分析

从你给出的代码片段来看,MioThread的后台线程应该是调用了registration.set_readiness(Ready::readable())(或其他就绪状态)来触发用户事件。但问题在于:当poll第一次返回这个事件后,你没有调用clear_readiness重置就绪状态,这个“就绪标记”会一直存在,下一次poll执行时就会再次触发事件。

解决方法:处理事件后清除就绪状态

在你处理完poll返回的用户事件后,一定要调用Registration::clear_readiness清除对应的就绪标记。这样Mio就不会重复触发该事件,直到你再次主动调用set_readiness。

这里给你补全并修复后的代码示例:

extern crate mio;
use mio::event::Evented;
use mio::{Events, Poll, PollOpt, Ready, Registration, Token};
use std::thread::{sleep, spawn, JoinHandle};
use std::time::{Duration, Instant};
use std::io;

#[derive(Debug)]
struct Output(u32, Duration);

pub struct MioThread {
    registration: Registration,
    handle: JoinHandle<Output>,
}

impl MioThread {
    pub fn new(i: u32) -> MioThread {
        let now = Instant::now();
        let (registration, set_readiness) = Registration::new2();
        
        let handle = spawn(move || {
            // 模拟后台任务耗时后触发用户事件
            sleep(Duration::from_secs(1));
            // 设置可读状态触发事件
            set_readiness.set_readiness(Ready::readable()).unwrap();
            Output(i, now.elapsed())
        });
        
        MioThread { registration, handle }
    }
}

impl Evented for MioThread {
    fn register(&self, poll: &Poll, token: Token, interest: Ready, opts: PollOpt) -> io::Result<()> {
        self.registration.register(poll, token, interest, opts)
    }

    fn reregister(&self, poll: &Poll, token: Token, interest: Ready, opts: PollOpt) -> io::Result<()> {
        self.registration.reregister(poll, token, interest, opts)
    }

    fn deregister(&self, poll: &Poll) -> io::Result<()> {
        self.registration.deregister(poll)
    }
}

fn main() -> io::Result<()> {
    let poll = Poll::new()?;
    let mut events = Events::with_capacity(1024);
    let thread = MioThread::new(1);
    
    // 水平触发模式(默认)注册事件
    poll.register(&thread, Token(0), Ready::readable(), PollOpt::level())?;
    
    // 第一次poll:会触发用户事件
    poll.poll(&mut events, None)?;
    for event in &events {
        if event.token() == Token(0) && event.readiness().is_readable() {
            println!("第一次触发用户事件");
            // 关键操作:处理完事件后清除就绪状态
            thread.registration.clear_readiness(Ready::readable())?;
        }
    }
    
    // 第二次poll:此时就绪状态已清除,不会再触发事件
    poll.poll(&mut events, Some(Duration::from_secs(1)))?;
    println!("第二次poll触发的事件数:{}", events.len());
    
    thread.handle.join().unwrap();
    Ok(())
}

额外小提示

Mio支持两种触发模式:

  • 水平触发(Level-triggered):默认模式,只要就绪状态存在,每次poll都会返回事件,适合大多数场景,需要手动清除状态避免重复触发。
  • 边缘触发(Edge-triggered):仅在状态从“非就绪”变为“就绪”时触发一次,注册时使用PollOpt::edge()即可,但要确保逻辑能覆盖所有场景(比如用户事件这种一次性触发的场景也适用)。

内容的提问来源于stack exchange,提问作者attdona

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 10:36:15