如何使用 cmd2 和 wxPython 创建非阻塞 GUI?

jst*_*ieb 2 python multithreading wxwidgets wxpython python-3.x

即使解释器和必须在主线程上运行,如何wxPythoncmd2解释器命令创建非阻塞GUI 窗口wx.App


我正在使用该cmd2模块在 Python 3.x 中创建命令行解释器。我的解释器中的一个命令将允许我创建一个“非阻塞” wxPythonGUI 窗口,该窗口带有一个在单独线程中运行的倒数计时器。它需要与主解释器线程分开运行,否则它将阻止在计时器运行时输入其他命令。

当我尝试使用该wxTimer对象来允许我的 GUI 更新其进度条并检查剩余时间时,我收到以下错误:

wx._core.wxAssertionError: C++ assertion "wxThread::IsMain()" failed at ..\..\src\common\timerimpl.cpp(60) in wxTimerImpl::Start(): timer can only be started from the main thread
Run Code Online (Sandbox Code Playgroud)

任何启动计时器的尝试都会破坏代码。例如,下面的简单代码不起作用:

import wx
import threading
import cmd2

class TimerFrame(wx.Frame):
    def __init__(self, time):
        super().__init__(None, title=str(time), size=(300, 300))
        self.InitUI()

    def InitUI(self):
        self.timer = wx.Timer(self, 1)
        self.timer.Start(100)
        self.Bind(wx.EVT_TIMER, self.OnTimer, id=1)
        self.Show()

    def OnTimer(self, event):
        print("Updating")

class Interpreter(cmd2.Cmd):
    def __init__(self):
        super().__init__()
        self.app = wx.App()

    def do_timer(self, _):
        threading.Thread(target=self.createTimer, args=(self.app,)).start()

    def createTimer(self, app):
        TimerFrame("Window Title")
        app.MainLoop()

if __name__ == "__main__":
    Interpreter().cmdloop()
Run Code Online (Sandbox Code Playgroud)

请注意,注释掉包含的行timer.Start(100)允许在单独的线程中成功创建窗口,但它们缺乏必要的Timer功能。


我尝试过的其他事情不起作用:

  • wx.App在新线程中创建而不是传递它(导致相同的错误,但对于MainLoop而不是Timer
  • wx.App在主线程中创建并在MainLoop那里运行(阻止命令行接受更多命令)
  • 在单独的线程中运行解释器命令循环(抱怨cmdloop必须从主线程运行)

aba*_*ert 5

文档中有一个关于将 cmd2 与事件循环集成的部分:

许多的Python并发库涉及或要求一个事件循环其中它们在诸如控制asynciogeventTwisted等。

虽然这是专门讨论以网络为中心的事件循环,但它实际上与 wx 之类的 GUI 事件循环存在相同的问题。

tl; dr 是,不是调用cmdloop(),而是阻塞整个主线程直到解释器退出,您只需调用preloop(),然后从回调中重复调用onecmd()(或onecmd_plus_hooks()runcmd_plus_hooks(),视情况而定)wx

换句话说,你cmd2wx一个驱动事件循环,所以wx循环可以接管线程。


wx也有一种手动驱动其事件循环的方法。查看wx.EventLoopBase和相关的类。(可能有一些很好的示例代码类似于cmd2文档中的代码,但您必须搜索它。)想法几乎相同:不是运行wx循环并永远阻塞线程,而是手动创建awx.EventLoopwx.EventLoopActivator,然后您从事件循环中反复调用while loop.Pending(): loop.Dispatch()(并且可能是app.ProcessIdle()cmd2

换句话说,你wxcmd2一个驱动事件循环,所以cmd2循环可以接管线程。


如果这些都不适合您,您可能可以使用multiprocessing代替threading. 这样,两个事件循环都在主线程中运行——但其中一个只是在子进程的主线程中运行。

这对于 GUI 应用程序来说可能是一个问题,因此放入wx子进程可能不起作用(或者,更糟糕的是,可能在某些平台/设置上工作但在其他平台/设置上不起作用,或者可能工作但经常神秘地失败......),但cmd2大概只需要查看标准输入/标准输出/tty,所以它可能会起作用。