如何实现带ESC终止功能的定时键盘事件触发程序?
如何中途终止定时触发键盘事件的程序?
看起来你现在的问题是循环被Thread.Sleep卡死了,没法及时响应ESC键的输入对吧?我给你两个实用的方案,完美解决你的需求——既能每2秒检测键盘输入,又能随时终止程序:
方案1:用CancellationTokenSource实现优雅终止(推荐)
这个方法是现代C#异步编程的标准做法,不会阻塞UI线程,还能干净利落地停止任务:
首先,你需要定义一个取消令牌源,然后用Task.Delay替代Thread.Sleep(它支持取消),同时每隔2秒检查ESC键的状态:
private CancellationTokenSource _cts; private async void StartAction() { _cts = new CancellationTokenSource(); int minutes = 2; var notepadProcess = Process.Start("notepad.exe"); try { for (int a = 0; a < minutes; a++) { for (int b = 0; b < 6; b++) { // 等待2秒,同时监听取消信号 await Task.Delay(2000, _cts.Token); // 检测ESC键是否按下 if (Console.KeyAvailable && Console.ReadKey(true).Key == ConsoleKey.Escape) { _cts.Cancel(); MessageBox.Show("程序已终止"); return; } // 在这里写你触发键盘事件的逻辑,比如 SendKeys.Send("^v"); } } } catch (TaskCanceledException) { // 任务被取消时,清理资源 notepadProcess?.CloseMainWindow(); notepadProcess?.Dispose(); } } // 还可以加个停止按钮的点击事件,手动终止 private void StopButton_Click(object sender, EventArgs e) { _cts?.Cancel(); }
方案2:全局键盘钩子(适合WinForms/WPF桌面程序)
如果你是做桌面应用,不想每隔2秒轮询,直接用全局键盘钩子监听ESC键更高效:
private LowLevelKeyboardProc _keyboardHook; private IntPtr _hookHandle = IntPtr.Zero; private Thread _workerThread; private volatile bool _isProgramRunning = true; // 键盘钩子回调委托 private delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam); private IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam) { // 只处理按键按下事件 if (nCode >= 0 && wParam == (IntPtr)0x0100) { int keyCode = Marshal.ReadInt32(lParam); if (keyCode == (int)Keys.Escape) { _isProgramRunning = false; UnhookWindowsHookEx(_hookHandle); } } return CallNextHookEx(_hookHandle, nCode, wParam, lParam); } private void StartAction() { // 注册全局键盘钩子 _keyboardHook = HookCallback; _hookHandle = SetHook(_keyboardHook); _isProgramRunning = true; var notepadProcess = Process.Start("notepad.exe"); // 把耗时操作放到后台线程,避免卡UI _workerThread = new Thread(() => { for (int a = 0; a < 2 && _isProgramRunning; a++) { for (int b = 0; b < 6 && _isProgramRunning; b++) { Thread.Sleep(2000); if (!_isProgramRunning) break; // 执行你的键盘事件逻辑 } } // 清理工作 notepadProcess?.CloseMainWindow(); notepadProcess?.Dispose(); UnhookWindowsHookEx(_hookHandle); }); _workerThread.Start(); } // 导入Win32 API实现钩子 [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId); [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool UnhookWindowsHookEx(IntPtr hhk); [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam); [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern IntPtr GetModuleHandle(string lpModuleName); private IntPtr SetHook(LowLevelKeyboardProc proc) { using (var currentProcess = Process.GetCurrentProcess()) using (var currentModule = currentProcess.MainModule) { return SetWindowsHookEx(13, proc, GetModuleHandle(currentModule.ModuleName), 0); } }
几个关键提醒
- 绝对不要在UI线程里用
Thread.Sleep,会让你的界面直接卡死,用async/await才是正确姿势 volatile关键字是为了确保_isProgramRunning变量在多线程下的可见性,避免线程读取到旧值- 不管用哪种方案,一定要记得在程序终止时清理资源,比如关闭启动的记事本进程,不然它会一直留在后台
内容的提问来源于stack exchange,提问作者Jay. K
相关产品推荐
相关产品推荐

