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

python-telegram-bot子线程运行触发signal报错问题咨询

问题描述

我有一个进程需要接收两个不同来源的请求,请求内容可简化为普通字符串消息。两个来源的请求都会存入PriorityQueue,由主进程统一处理。其中一个请求源是基于python-telegram-bot包搭建的Telegram机器人,两个请求源都需要运行自身的事件循环才能产出请求,因此我计划将二者放在独立线程中启动。

对应设计思路的伪代码如下:

queue = PriorityQueue()
handler = RequestHandler(queue)
telegramRequester = TelegramRequester(queue)
anotherRequester = SomeOtherSourceRequester(queue)

telegramRequester.start()        # 启动Telegram机器人轮询/空闲循环
anotherRequester.start()         # 启动另一个请求源的事件循环
handler.handleRequestsLoop()     # 启动请求处理循环

Telegram机器人及对应请求器的实现大致如下:

class Bot:
    def __init__(self):
        self._updater = telegram.ext.Updater("my api token", use_context=True)
    
    def run(self):
        self._updater.start_polling(drop_pending_updates=True)
        self._updater.idle()

    def otherFunctions(self):
        # 包含命令注册、命令处理器绑定等逻辑
        # 机器人本身的功能已经测试通过,可正常运行


class TelegramRequester:
    def __init__(self, queue:RequestQueue) -> None:
        self._queue:RequestQueue = requestQueue
        self._bot = Bot()
        self._thread:threading.Thread = threading.Thread(target=self._bot.run)

    def start(self):
        if not self._thread.is_alive():
            self._thread.start()

运行上述代码时抛出如下错误:

File "...\myscript.py", line 83, in run
    self._updater.idle()
File "...\env\lib\site-packages\telegram\ext\updater.py", line 885, in idle
    signal(sig, self._signal_handler)
File "C:\Program Files\aaaProgrammieren\Python\Python3_9\lib\signal.py", line 47, in signal
    handler = _signal.signal(_enum_to_int(signalnum), _enum_to_int(handler))
ValueError: signal only works in main thread of the main interpreter

我是第一次接触Telegram API,也是第一次开发多线程并行程序,没有Web/网络编程相关经验。想确认是Telegram机器人本身就不支持在独立线程运行,还是我遗漏了某些简单配置,就可以实现上述多请求源的架构设计?

问题原因

报错和Telegram机器人本身是否支持子线程运行无关,核心是Updater.idle()方法的默认机制限制:

  • idle()默认会注册SIGINT、SIGTERM这类系统信号处理器,用来监听终止信号实现优雅退出
  • Python的信号处理器只允许在主线程注册,子线程调用signal.signal()会直接抛出上述ValueError
解决方案

不需要调整整体的多请求源架构,只需要修改Bot的run方法逻辑,避开在子线程调用信号注册逻辑即可,有两种常用实现方式:

方案1:手动实现子线程阻塞等待,替换idle()调用

idle()除了注册信号处理,核心作用就是阻塞线程保持运行,直到收到终止信号。在子线程运行时可以自行实现阻塞逻辑,不需要调用idle():

import time
from threading import Event

class Bot:
    def __init__(self):
        self._updater = telegram.ext.Updater("my api token", use_context=True)
        self._stop_event = Event()
    
    def run(self):
        self._updater.start_polling(drop_pending_updates=True)
        # 循环阻塞等待停止信号,替换原idle()逻辑
        while not self._stop_event.is_set():
            time.sleep(0.5)
        # 收到停止信号后主动释放updater资源
        self._updater.stop()

    def stop(self):
        self._stop_event.set()

这种方式可控性强,可以在主线程需要退出时主动调用bot.stop()触发子线程清理逻辑,避免僵尸线程。

方案2:调用idle()时关闭信号注册参数

v13及以上版本的python-telegram-bot已经提供了原生参数支持关闭信号注册,只需要在调用idle()时传入signals=()即可:

def run(self):
    self._updater.start_polling(drop_pending_updates=True)
    # 传入空信号列表,不会尝试注册信号处理器
    self._updater.idle(signals=())

这种方式改造成本最低,不需要额外编写阻塞逻辑,注意自行处理程序退出时的机器人资源释放即可。


内容的提问来源于stack exchange,提问作者LCsa

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.03 00:39:26