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

时间:2018-05-02 22:44:09

标签: python multithreading python-3.x wxpython wxwidgets

如果必须在主线程上运行解释器和wxPython,如何从cmd2解释器命令创建非阻塞wx.App GUI窗口?

我正在使用cmd2模块在​​Python 3.x中创建命令行解释器。我的解释器中的一个命令将允许我创建一个“非阻塞”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.App而不是传递它(导致相同的错误但是MainLoop而不是Timer
  • 在主线程中创建wx.App并在其中运行MainLoop(阻止命令行接受其他命令)
  • 在一个单独的线程中运行解释器命令循环(抱怨必须从主线程运行cmdloop

1 个答案:

答案 0 :(得分:3)

文档中有一节Integrating cmd2 with event loops

  

许多Python并发库涉及或需要一个它们可以控制的事件循环,例如asynciogeventTwisted等。

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

tl; dr是,而不是调用cmdloop(),阻止整个主线程,直到解释器退出,你只需拨打preloop(),然后你反复拨打onecmd()(或{来自onecmd_plus_hooks()回调的{1}}或runcmd_plus_hooks()

换句话说,您从wx循环驱动cmd2事件循环,因此wx循环可以接管该主题。

wx还有一种手动驱动其事件循环的方法。请参阅wx.EventLoopBase及相关课程。 (可能有一些很好的示例代码类似于wx文档中的代码,但您必须搜索它。)这个想法几乎相同:而不是运行cmd2永远循环并阻止线程,您手动创建wxwx.EventLoop,然后从wx.EventLoopActivator事件重复调用while loop.Pending(): loop.Dispatch()(可能app.ProcessIdle())循环。

换句话说,您从cmd2循环驱动wx事件循环,因此cmd2循环可以接管该主题。

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

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