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

Windows11 Python非阻塞并发弹出多个系统通知的方案咨询

问题根因

报错来自两个核心问题:

  • 所有通知进程固定使用相同的窗口类名PythonTaskbar、固定托盘图标ID0,多进程同时启动时会出现托盘资源互相覆盖,销毁阶段调用Shell_NotifyIcon(NIM_DELETE)找不到对应合法资源,直接抛出未指定错误。
  • 原代码用time.sleep硬阻塞等待超时后直接销毁窗口,没有启动Windows标准消息循环,托盘相关的系统回调消息无法按正确时序处理,多实例并发时消息处理逻辑乱序触发异常。
修复方案

方案1:改造原有原生win32api代码

核心修改点:为每个通知实例生成唯一的窗口类名、托盘图标ID避免资源冲突;用窗口定时器+标准消息循环替代time.sleep硬阻塞,保证消息按正确时序处理;清理阶段加异常捕获兜底偶发资源释放失败问题。
修复后的balloontip.py完整代码:

from win32api import *
from win32gui import *
import win32con
import sys, os
import random
import string

# 生成随机字符串做实例唯一标识
def gen_random_str(length=8):
    return ''.join(random.choice(string.ascii_letters) for _ in range(length))

class WindowsBalloonTip:
    def __init__(self, title, msg, timeout):
        self.hwnd = None
        self.hinst = GetModuleHandle(None)
        self.class_name = f"PythonTaskbar_{gen_random_str()}"
        self.icon_id = random.randint(1, 10000)
        
        message_map = {
                win32con.WM_DESTROY: self.OnDestroy,
                win32con.WM_TIMER: self.OnTimer,
        }
        wc = WNDCLASS()
        wc.hInstance = self.hinst
        wc.lpszClassName = self.class_name
        wc.lpfnWndProc = message_map
        RegisterClass(wc)

        style = win32con.WS_OVERLAPPED | win32con.WS_SYSMENU
        self.hwnd = CreateWindow( self.class_name, "Taskbar", style,
                0, 0, win32con.CW_USEDEFAULT, win32con.CW_USEDEFAULT,
                0, 0, self.hinst, None)
        UpdateWindow(self.hwnd)
        
        iconPathName = os.path.abspath(os.path.join(sys.path[0], "balloontip.ico"))
        icon_flags = win32con.LR_LOADFROMFILE | win32con.LR_DEFAULTSIZE
        try:
           hicon = LoadImage(self.hinst, iconPathName,
                    win32con.IMAGE_ICON, 0, 0, icon_flags)
        except:
          hicon = LoadIcon(0, win32con.IDI_APPLICATION)
        
        flags = NIF_ICON | NIF_MESSAGE | NIF_TIP
        nid = (self.hwnd, self.icon_id, flags, win32con.WM_USER+20, hicon, "tooltip")
        Shell_NotifyIcon(NIM_ADD, nid)
        Shell_NotifyIcon(NIM_MODIFY,
                         (self.hwnd, self.icon_id, NIF_INFO, win32con.WM_USER+20,
                          hicon, "Balloon tooltip", msg, 200, title))

        # 定时器替代阻塞sleep,保证消息循环正常运行
        SetTimer(self.hwnd, 1, int(timeout*1000), None)
        PumpMessages()

    def OnTimer(self, hwnd, msg, wparam, lparam):
        KillTimer(hwnd, 1)
        DestroyWindow(hwnd)

    def OnDestroy(self, hwnd, msg, wparam, lparam):
        nid = (self.hwnd, self.icon_id)
        # 清理操作加异常兜底,避免偶发资源不存在报错
        try:
            Shell_NotifyIcon(NIM_DELETE, nid)
        except Exception:
            pass
        PostQuitMessage(0)

def balloon_tip(title, msg, timeout):
    WindowsBalloonTip(title, msg, timeout)

if __name__ == '__main__':
    balloon_tip(sys.argv[1], sys.argv[2], float(sys.argv[3]))

改完后无需修改主业务代码,不管是两个独立脚本同时触发通知,还是单脚本移除wait()连续启动多个通知进程,都不会触发报错,所有通知会按系统规则排队弹出,主进程也不需要等待通知执行完成就能继续跑后续逻辑。

方案2:使用封装好的通知库(成本更低)

如果不想手动维护win32api底层逻辑,可以直接用适配Win10/11的通知库,天生支持多通知并发,不需要自己处理资源冲突、消息循环问题。
先安装依赖:
pip install win11toast
后续不需要单独维护balloontip.py子进程脚本,直接在主代码里调用即可,调用不会阻塞主逻辑运行:

from win11toast import toast

# 连续触发多个通知无冲突,不需要wait等待
toast('alala', 'code 1')
toast('alala', 'code 2')
toast('alala', 'code 3')

该库底层已经做好了资源隔离和消息处理,短时间内触发大量通知也不会抛错,稳定性比手动封装win32api更高。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 06:24:17