代码之家  ›  专栏  ›  技术社区  ›  jstrieb

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

  •  2
  • jstrieb  · 技术社区  · 6 年前

    如何创建非阻塞 wxPython 来自的GUI窗口 cmd2 即使解释器和 wx.App 必须在主线程上运行?


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

    当我尝试使用 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
    

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

    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()
    

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


    我尝试过的其他不起作用的东西:

    • 创建 wx。应用程序 在新线程中,而不是传递它(导致相同的错误,但对于 MainLoop 而不是 计时器 )
    • 创建 wx。应用程序 在主线程中运行 主回路 此处(阻止命令行接受更多命令)
    • 在单独的线程中运行解释器命令循环(抱怨 cmdloop 必须从主线程运行)
    1 回复  |  直到 4 年前
        1
  •  5
  •   abarnert    6 年前

    文档中有一个关于 Integrating cmd2 with event loops :

    许多Python并发库都涉及或需要一个由它们控制的事件循环,例如 asyncio ,则, gevent ,则, Twisted

    虽然这是专门讨论以网络为中心的事件循环,但对于像wx这样的GUI事件循环,这实际上是同一个问题。

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

    换句话说,您驾驶 cmd2 来自的事件循环 wx公司 一个,所以 wx公司 循环可以接管线程。


    wx公司 还有一种手动驱动其事件循环的方法。看见 wx.EventLoopBase 和相关类别。(可能有一些很好的示例代码类似于 cmd2 文档,但您必须搜索它。)想法大致相同:不是运行 wx公司 循环并永远阻塞线程,您可以手动创建 wx.EventLoop wx.EventLoopActivator ,然后您反复呼叫 while loop.Pending(): loop.Dispatch() (而且可能 app.ProcessIdle() )从 cmd2 事件循环。

    换句话说,您驾驶 wx公司 来自的事件循环 cmd2 一个,所以 cmd2 循环可以接管线程。


    如果这两种方法都不适合你,你可以使用 multiprocessing 而不是 threading 。这样,两个事件循环都在主线程中运行,但其中一个仅在子进程的主线程中运行。

    这可能是GUI应用程序的一个问题 wx公司 在子进程中,可能不起作用(或者更糟的是,可能在某些平台/设置上起作用,但在其他平台/设置上不起作用,或者可能起作用,但经常神秘地失败),但是 cmd2 大概只需要看到stdin/stdout/tty,所以它可能会工作。