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

如何在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是阻塞式方法,会暂停当前主线程直到用户输入回车,这时候主线程无法同时检测按键输入。

核心思路

  1. 启动一个后台线程,持续监听控制台按键,一旦检测到ESC键,设置一个全局标志位标记流程取消。
  2. 在主线程的每个输入、处理步骤前后,检查这个标志位,若已触发则立即终止添加流程。
  3. 确保标志位的线程安全性,避免多线程间的缓存不一致问题。

修改后的代码示例

首先在类级别定义一个线程安全的标志位:

// 标记是否取消添加流程,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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 12:34:57