为何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
相关产品推荐
相关产品推荐

