使用多线程后Python/WxPython程序关闭时冻结,如何正确终止线程与资源?
问题修复:WxPython邮箱监控程序关闭时冻结无响应
问题根源分析
你的程序关闭时冻结主要源于以下几个问题:
time.sleep(60)是阻塞调用,线程收到停止信号后必须等待sleep结束才能退出,导致关闭过程卡顿- 在WxPython窗口关闭事件中使用
sys.exit(0)不当,跳过了正常的资源清理流程 play_noise函数定义了参数但调用时未传递,且playsound的阻塞执行拖慢了后台线程响应速度- 关闭窗口时仅提示用户手动停止程序,未自动处理线程终止和资源释放
修复方案
- 用
threading.Event替代布尔值标记线程状态,实现可中断的等待逻辑 - 替换
time.sleep(60)为event.wait(60),让线程能立即响应停止信号 - 将音频播放放入独立子线程,避免阻塞邮箱检查流程
- 调整窗口关闭逻辑:自动终止后台线程并等待其退出后再销毁窗口
- 移除
sys.exit(0),改用WxPython标准的self.Destroy()方法关闭窗口
修复后的完整代码
import imaplib import email import threading from playsound import playsound import wx login_file = "Login.txt" song = "Songs/SalesSound.wav" imap_server = "imap.gmail.com" imap_ssl_port = 993 email_address = '' password = '' sender_email = ["email1", "email2", "email3"] def play_noise(): # 将音频播放放入守护线程,避免阻塞邮箱检查线程 threading.Thread(target=lambda: playsound(song), daemon=True).start() def check_for_new_emails(mail): mail.select('inbox') # 合并多发件人搜索条件,减少IMAP服务器交互次数 sender_filter = " OR ".join([f'FROM "{sender}"' for sender in sender_email]) result, data = mail.search(None, f'(UNSEEN {sender_filter})') email_ids = data[0].split() for email_id in email_ids: result, message_data = mail.fetch(email_id, '(BODY[HEADER.FIELDS (SUBJECT FROM)])') raw_email = message_data[0][1].decode("utf-8") email_message = email.message_from_string(raw_email) subject = email_message["Subject"] print(f"New email from {email_message['From']} with subject: {subject}") play_noise() print(email_message) mail.store(email_id, '+FLAGS', '\\Seen') def close_mail_connection(mail): try: mail.close() except Exception: pass # 忽略关闭时的异常,避免程序崩溃 try: mail.logout() except Exception: pass class SalesIncomingGUI(wx.Frame): def __init__(self, parent, id): self.mail = None self.mail_thread = None self.stop_event = threading.Event() # 用于线程终止的信号量 wx.Frame.__init__(self, parent, id, 'Upfluence, Where Salespeople Matter', size=(800, 600)) self.panel = wx.Panel(self) stop_program = wx.Button(self.panel, label="Stop", pos=(450, 150), size=(50, 50)) start_program = wx.Button(self.panel, label="Start", pos=(350, 150), size=(50, 50)) self.Bind(wx.EVT_BUTTON, self.stop_loop, stop_program) self.Bind(wx.EVT_BUTTON, self.start_loop, start_program) self.Bind(wx.EVT_CLOSE, self.close_window) def start_loop(self, event): if self.mail_thread is None or not self.mail_thread.is_alive(): self.stop_event.clear() self.mail = imaplib.IMAP4_SSL(imap_server) self.mail.login(email_address, password) # 标记为守护线程,主程序退出时自动终止 self.mail_thread = threading.Thread(target=self.run_loop, args=(self.mail,), daemon=True) self.mail_thread.start() def stop_loop(self, event): if self.mail_thread and self.mail_thread.is_alive(): self.stop_event.set() # 等待线程退出,设置超时避免无限等待 self.mail_thread.join(timeout=5) def run_loop(self, mail): while not self.stop_event.is_set(): print("Running") try: check_for_new_emails(mail) except Exception as e: print(f"Email check error: {e}") # 等待60秒或收到停止信号立即返回 self.stop_event.wait(60) close_mail_connection(mail) def close_window(self, event): # 先终止后台线程再关闭窗口 if self.mail_thread and self.mail_thread.is_alive(): self.stop_event.set() self.mail_thread.join(timeout=5) self.Destroy() if __name__ == '__main__': with open(login_file, 'r') as file: for line in file: key, value = line.strip().split(': ') if key == 'Email_Address': email_address = value elif key == 'Password': password = value app = wx.App() frame = SalesIncomingGUI(parent=None, id=-1) frame.Show() app.MainLoop()
额外优化说明
- 合并多发件人搜索请求,减少与IMAP服务器的交互次数,提升效率
- 为资源关闭步骤添加异常捕获,避免因连接异常导致程序崩溃
- 音频播放使用守护线程,不影响主程序的正常退出流程
- 后台线程标记为守护线程,确保主程序退出时线程自动终止
内容的提问来源于stack exchange,提问作者Skyler Knoflick
相关产品推荐
相关产品推荐

