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

使用多线程后Python/WxPython程序关闭时冻结,如何正确终止线程与资源?

问题修复:WxPython邮箱监控程序关闭时冻结无响应

问题根源分析

你的程序关闭时冻结主要源于以下几个问题:

  • time.sleep(60)是阻塞调用,线程收到停止信号后必须等待sleep结束才能退出,导致关闭过程卡顿
  • 在WxPython窗口关闭事件中使用sys.exit(0)不当,跳过了正常的资源清理流程
  • play_noise函数定义了参数但调用时未传递,且playsound的阻塞执行拖慢了后台线程响应速度
  • 关闭窗口时仅提示用户手动停止程序,未自动处理线程终止和资源释放

修复方案

  1. 用threading.Event替代布尔值标记线程状态,实现可中断的等待逻辑
  2. 替换time.sleep(60)为event.wait(60),让线程能立即响应停止信号
  3. 将音频播放放入独立子线程,避免阻塞邮箱检查流程
  4. 调整窗口关闭逻辑:自动终止后台线程并等待其退出后再销毁窗口
  5. 移除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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 20:05:32