如何通过Telegram Bot立即终止Windows上的Python进程?
问题
用Python 3.11开发了一个Telegram Bot,用于控制Windows机器上运行的Python脚本。点击Stop按钮时,无法立即终止目标脚本进程,必须等脚本执行完毕才会响应停止指令,但直接在终端或其他Python脚本里用taskkill能正常终止该进程,怀疑是async/await的使用方式有误。
原因分析
- 同步阻塞占用事件循环:
stop_command中使用了同步的subprocess.run,在异步环境下会阻塞整个事件循环,导致Bot无法及时处理终止后的后续逻辑,也无法响应其他请求。 - 输出读取逻辑未响应停止信号:
send_output函数里的await stream.readline()是阻塞式调用,即使进程被终止,该调用可能不会立即返回,导致函数无法及时退出,run_script中的await asyncio.gather会一直等待输出流读取完成,才会执行后续的current_process.wait()。
解决方案
1. 异步执行taskkill,避免阻塞事件循环
将stop_command中的同步subprocess.run替换为异步的asyncio.create_subprocess_exec,确保事件循环不被阻塞。
2. 在输出读取循环中加入停止检查
修改send_output函数,每次循环先检查stop_requested状态,同时给stream.readline()设置超时,避免一直阻塞在无输出的场景。
3. 优化进程终止后的清理逻辑
确保进程被终止后,及时清理current_process并反馈状态给用户。
修改后的关键代码
调整stop_command为异步执行
@restricted async def stop_command(update: Update, context: ContextTypes.DEFAULT_TYPE): """Stop the current process.""" global current_process, stop_requested print("Stop button clicked.") if current_process is not None: try: # 改用异步方式执行taskkill kill_proc = await asyncio.create_subprocess_exec( 'taskkill', '/F', '/T', '/PID', str(current_process.pid), stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE ) stdout, stderr = await kill_proc.communicate() stop_requested = True print("Process killed.") await update.message.reply_text('Process killed.') # 打印taskkill输出 print("Output of taskkill :") print(stdout.decode('utf-8', errors='ignore')) print(stderr.decode('utf-8', errors='ignore')) current_process = None except Exception as e: print(f"Error when stopping the process: {e}") await update.message.reply_text(f"Error when stopping the process: {e}") else: print("No processes running.") await update.message.reply_text('No running processes.')
修改send_output函数,加入停止检查
async def send_output(stream, update, context, stream_name): while True: # 每次循环先检查是否请求停止 if stop_requested: break # 设置超时读取,避免一直阻塞 try: line = await asyncio.wait_for(stream.readline(), timeout=1.0) except asyncio.TimeoutError: continue if not line: break try: decoded_output = line.decode('utf-8', errors='ignore').strip() print(f"{stream_name}: {decoded_output}") await context.bot.send_message(chat_id=update.effective_chat.id, text=f"{stream_name}: {decoded_output}") except UnicodeDecodeError as e: print(f"Decoding error : {e}") if stop_requested: print(f"{stream_name}: Process stopped by the user.") await context.bot.send_message(chat_id=update.effective_chat.id, text=f"{stream_name}: Process stopped by the user.")
调整run_script中的逻辑
await asyncio.gather( send_output(current_process.stdout, update, context, "STDOUT"), send_output(current_process.stderr, update, context, "STDERR"), return_exceptions=True # 避免单个任务出错导致整个gather失败 ) if stop_requested: # 主动停止时直接清理进程并反馈 current_process = None await context.bot.send_message(chat_id=update.effective_chat.id, text=f'Script stopped by the user.', reply_markup=reply_markup) return return_code = await current_process.wait() current_process = None print(f"Script completed with return code : {return_code}") if return_code != 0: await context.bot.send_message(chat_id=update.effective_chat.id, text=f'Script terminated with errors, return code : {return_code}', reply_markup=reply_markup) else: await context.bot.send_message(chat_id=update.effective_chat.id, text=f'Script completed successfully, return code : {return_code}', reply_markup=reply_markup)
说明
asyncio.wait_for给stream.readline()设置超时,确保即使没有输出,也能定期检查stop_requested状态,避免一直阻塞。return_exceptions=True保证单个输出流读取任务出错时,其他任务能继续执行,不会导致整个gather失败。- 异步执行
taskkill确保事件循环能继续处理Bot的其他消息请求,提升响应速度。
内容的提问来源于stack exchange,提问作者Fokda
相关产品推荐
相关产品推荐

