【问题标题】:How can I create a non-blocking GUI with cmd2 and wxPython?如何使用 cmd2 和 wxPython 创建非阻塞 GUI?
【发布时间】:2018-10-13 03:37:17
【问题描述】:

即使解释器和wx.App 都必须在主线程上运行,如何通过cmd2 解释器命令创建非阻塞wxPython 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 必须从主线程运行)

【问题讨论】:

  • 至于为什么会死掉,timer使用timer信号,python将信号限制在主线程。

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


【解决方案1】:

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

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

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

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

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


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

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


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

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

【讨论】:

  • 我最终使用multiprocessing 在子进程中运行相当简单的 Timer GUI(主要是为了避免大型代码重构)。我在 Windows 和 OS X 上测试过,性能是一致的。感谢您快速而全面的回复!
  • @jstrieb 我主要担心的是 OS X(应用程序与进程的区别、隐藏与窗口服务器通信的 GUI API、沙盒等);如果它在那里对你有用,你应该没问题。
猜你喜欢
  • 2012-06-11
  • 1970-01-01
  • 2014-05-24
  • 1970-01-01
  • 1970-01-01
  • 2012-03-21
相关资源
最近更新 更多