主线程读取stdin时,如何在后台线程可靠使用PeekConsoleInput?
如何在保留主线程控制台输入打印的同时获取INPUT_RECORD?
我需要实时从控制台输入缓冲区获取INPUT_RECORD(优先仅获取KEY_EVENT_RECORD),同时不阻止主线程读取这些记录并将字符打印到控制台屏幕缓冲区。
目前我只能实现二者其一:要么用PeekConsoleInput(或ReadConsoleInput)获取INPUT_RECORD,要么让主线程处理输入字符的读取与屏幕打印。
当前实现的控制台输入打印行为
我可以通过以下两行代码让控制台应用将用户输入的内容(包括按退格键/删除键删除字符、按方向键插入字符等操作)打印到屏幕:
let mut line = String::new(); std::io::stdin().read_line(&mut line).unwrap();

我希望保留这一行为,同时从控制台输入缓冲区获取INPUT_RECORD。获取到这些记录后,我可以将其发送给子进程,通过WriteConsoleInput在多个控制台中同步复现用户输入。
遇到的问题
我尝试在后台线程中使用PeekConsoleInput,主线程保留上述代码,但主线程似乎更快地将INPUT_RECORD移出输入缓冲区,导致后台线程没有记录可查看。
附带代码展示了只要没有其他线程移除记录(flush = true 对比 flush = false),在后台线程运行PeekConsoleInput就能成功查看INPUT_RECORD。
如何在让主线程负责将输入字符打印到屏幕的同时,可靠地查看控制台输入缓冲区并获取INPUT_RECORD?
依赖配置(Cargo.toml)
[dependencies] tokio = {version = "1.27.0", features = ["macros", "rt-multi-thread", "time"]} [dependencies.windows] version = "0.44.0" features = [ "Win32_Foundation", "Win32_System_Console", ]
示例代码(main.rs)
use std::time::Duration; use windows::Win32::System::Console::{ FlushConsoleInputBuffer, GetStdHandle, PeekConsoleInputW, INPUT_RECORD, STD_INPUT_HANDLE, }; async fn peek_console_input(nb_of_records_to_read: u32, flush: bool) { const NB_EVENTS: usize = 2; let mut nb_of_records_read = 0; let console_handle = unsafe { GetStdHandle(STD_INPUT_HANDLE).unwrap() }; loop { let mut input_buffer: [INPUT_RECORD; NB_EVENTS] = [INPUT_RECORD::default(); NB_EVENTS]; let mut number_of_events_read = 0; unsafe { PeekConsoleInputW( console_handle, &mut input_buffer, &mut number_of_events_read, ) .expect("Failed to read console input"); } if number_of_events_read > 0 as u32 { if flush { unsafe { FlushConsoleInputBuffer(console_handle); }; } nb_of_records_read += number_of_events_read; for record in input_buffer { let event = unsafe { record.Event.KeyEvent }; println!( "key_down: {}, unicode_char: {}", event.bKeyDown.as_bool(), unsafe { event.uChar.UnicodeChar } ); } } if nb_of_records_read >= nb_of_records_to_read { break; } } } #[tokio::main] async fn main() { let nb_of_records_to_read = 8; for flush in [true, false] { println!( "Spawning a thread peeking at the console input buffer \ for the next {nb_of_records_to_read} input records \ (key down and up count as 1 record each)" ); let join_handle = tokio::spawn(peek_console_input(nb_of_records_to_read, flush)); if flush { println!( "Putting main thread to sleep for 2 milliseconds at a time \ while waiting for the background thread to finish." ); println!("Please press any combination of keys and behold the output"); while !join_handle.is_finished() { tokio::time::sleep(Duration::from_millis(2)).await; } } else { println!("Main thread is reading a line from stdin, please start pressing keys"); let mut line = String::new(); std::io::stdin().read_line(&mut line).unwrap(); println!("read line: {}", line); } join_handle.abort(); join_handle.await.unwrap(); } }
内容的提问来源于stack exchange,提问作者whme
相关产品推荐
相关产品推荐

