如何在C#控制台输入时检测ESC按键以取消员工添加流程?
C#控制台应用实现ESC键随时取消员工添加流程
问题描述
开发了一款可添加员工的C#控制台应用,添加员工时程序会依次询问姓名、出生日期(DOB)等信息。目前输入有误无法即时回退(后续可能实现该功能),希望用户可随时按下ESC键取消整个添加流程。但无论在Console.ReadLine输入状态,还是输入完成后的Console.WriteLine状态,现有ESC检测逻辑均无法生效。尝试过用Console.ReadKey做循环、Console.KeyAvailable,但都不适用。请问是否需要使用线程实现?能否在用户输入字符串时持续检测ESC按键?
现有代码
Console.WriteLine(@"****adding employee***** ________________"); //***Trying to have program continuously check if esc is ever pressed if so, stops the process of adding. do { Console.Write("name: "); first = Console.ReadLine(); indexFirst = first.IndexOfAny(invalid); Console.Write("Last: "); last = Console.ReadLine(); indexLast = last.IndexOfAny(invalid); if (indexFirst > -1 || indexLast > -1) { Console.Clear(); Console.Write("First or Last name entry has an invalid character"); Console.ReadLine(); Console.Clear(); AddEmployee(); } Console.WriteLine("Generating ID..."); Console.ReadKey(); id = Convert.ToString(IdGenerator()); Console.WriteLine($"ID number: {id}"); Console.ReadKey(); Console.WriteLine("Date of birth (MM/DD/YYYY)"); while (true) { try { birth = DateOnly.Parse(Console.ReadLine()); break; } catch (Exception ex) { Console.Write("Please include the \"/\" symbol to separate the month day and year."); } } Console.Write("Address: "); address = Console.ReadLine(); Console.WriteLine("Is the address correct? Type \"yes\" or \"no\". "); answerCheckAddress = Console.ReadLine(); answerCheckAddress = answerCheckAddress.ToLower(); while (answerCheckAddress == "no") { Console.Write("\bRe-enter the address: "); address = Console.ReadLine(); Console.WriteLine("Is the address correct? Type \"yes\" or \"no\". "); answerCheckAddress = Console.ReadLine(); answerCheckAddress = answerCheckAddress.ToLower(); } Console.Write("Social Security Number: "); social = Console.ReadLine(); int socialCheck = social.Length; while (socialCheck > 9 || socialCheck < 9) { Console.WriteLine("Social has too many or too few numbers."); Console.Write("Please re-enter Social Security Number: "); social = Console.ReadLine(); socialCheck = social.Length; } employees.Add(new Employee(first, last, id, birth, address, social)); Console.WriteLine("Employee added...Press enter to continue."); Console.ReadKey(); Console.Clear(); EmployeeMenu(); } while (Console.ReadKey().Key != ConsoleKey.Escape);
解决方案
要实现随时检测ESC键并终止流程,必须使用多线程。因为Console.ReadLine是阻塞式方法,会暂停当前主线程直到用户输入回车,这时候主线程无法同时检测按键输入。
核心思路
- 启动一个后台线程,持续监听控制台按键,一旦检测到ESC键,设置一个全局标志位标记流程取消。
- 在主线程的每个输入、处理步骤前后,检查这个标志位,若已触发则立即终止添加流程。
- 确保标志位的线程安全性,避免多线程间的缓存不一致问题。
修改后的代码示例
首先在类级别定义一个线程安全的标志位:
// 标记是否取消添加流程,volatile保证多线程间的可见性 private static volatile bool _isAddCanceled = false;
然后修改AddEmployee方法,加入ESC检测线程:
void AddEmployee() { // 重置取消标志 _isAddCanceled = false; // 启动ESC监听后台线程 Thread escListener = new Thread(() => { while (!_isAddCanceled) { if (Console.KeyAvailable) { ConsoleKeyInfo key = Console.ReadKey(true); // true不显示按键输入 if (key.Key == ConsoleKey.Escape) { _isAddCanceled = true; Console.WriteLine("\n添加流程已取消!"); break; } } Thread.Sleep(50); // 降低CPU占用,50ms间隔足够及时响应 } }); escListener.IsBackground = true; // 后台线程随主程序退出自动终止 escListener.Start(); Console.WriteLine(@"****adding employee***** ________________"); try { Console.Write("name: "); first = Console.ReadLine(); if (_isAddCanceled) goto CancelProcess; indexFirst = first.IndexOfAny(invalid); if (_isAddCanceled) goto CancelProcess; Console.Write("Last: "); last = Console.ReadLine(); if (_isAddCanceled) goto CancelProcess; indexLast = last.IndexOfAny(invalid); if (_isAddCanceled) goto CancelProcess; if (indexFirst > -1 || indexLast > -1) { Console.Clear(); Console.Write("First or Last name entry has an invalid character"); Console.ReadLine(); Console.Clear(); if (_isAddCanceled) goto CancelProcess; AddEmployee(); // 递归重新添加,注意此处可优化为循环避免栈溢出 return; } Console.WriteLine("Generating ID..."); if (_isAddCanceled) goto CancelProcess; Console.ReadKey(); if (_isAddCanceled) goto CancelProcess; id = Convert.ToString(IdGenerator()); Console.WriteLine($"ID number: {id}"); if (_isAddCanceled) goto CancelProcess; Console.ReadKey(); if (_isAddCanceled) goto CancelProcess; Console.WriteLine("Date of birth (MM/DD/YYYY)"); while (true) { try { birth = DateOnly.Parse(Console.ReadLine()); break; } catch (Exception ex) { Console.Write("Please include the \"/\" symbol to separate the month day and year."); } if (_isAddCanceled) goto CancelProcess; } Console.Write("Address: "); address = Console.ReadLine(); if (_isAddCanceled) goto CancelProcess; Console.WriteLine("Is the address correct? Type \"yes\" or \"no\". "); answerCheckAddress = Console.ReadLine(); if (_isAddCanceled) goto CancelProcess; answerCheckAddress = answerCheckAddress.ToLower(); while (answerCheckAddress == "no") { Console.Write("\bRe-enter the address: "); address = Console.ReadLine(); if (_isAddCanceled) goto CancelProcess; Console.WriteLine("Is the address correct? Type \"yes\" or \"no\". "); answerCheckAddress = Console.ReadLine(); if (_isAddCanceled) goto CancelProcess; answerCheckAddress = answerCheckAddress.ToLower(); } Console.Write("Social Security Number: "); social = Console.ReadLine(); if (_isAddCanceled) goto CancelProcess; int socialCheck = social.Length; while (socialCheck > 9 || socialCheck < 9) { Console.WriteLine("Social has too many or too few numbers."); Console.Write("Please re-enter Social Security Number: "); social = Console.ReadLine(); if (_isAddCanceled) goto CancelProcess; socialCheck = social.Length; } employees.Add(new Employee(first, last, id, birth, address, social)); Console.WriteLine("Employee added...Press enter to continue."); Console.ReadKey(); } finally { CancelProcess: // 终止监听线程 _isAddCanceled = true; escListener.Join(); // 等待线程完全终止 Console.Clear(); EmployeeMenu(); } }
关键说明
- 线程安全:用
volatile修饰_isAddCanceled,确保主线程和监听线程能实时读取到该变量的最新值。 - 后台线程:设置
IsBackground = true,避免程序退出时线程仍在运行导致进程无法正常结束。 - CPU占用优化:监听线程中加入
Thread.Sleep(50),避免无间隔循环导致CPU占用过高。 - 流程终止:使用
goto快速跳转到清理逻辑,简化嵌套循环中的退出操作(也可使用多个break或提前return,根据个人代码习惯调整)。
为什么之前的方法无效
原来的do-while循环仅在整个添加流程完成后才检测ESC键,而Console.ReadLine、Console.ReadKey等阻塞方法会暂停主线程,导致在用户输入过程中根本无法检测到ESC按键,自然无法实现“随时取消”的需求。
内容的提问来源于stack exchange,提问作者FloorSign
相关产品推荐
相关产品推荐

