【问题标题】:RuntimeError: main thread is not in main loopRuntimeError:主线程不在主循环中
【发布时间】:2013-01-19 14:31:29
【问题描述】:

当我打电话时

self.client = ThreadedClient() 

在我的 Python 程序中,出现错误

"RuntimeError: 主线程不在主循环中"

我已经做了一些谷歌搜索,但不知何故我犯了一个错误......有人可以帮我吗?

完全错误:

Exception in thread Thread-1:
    Traceback (most recent call last):
    File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/threading.py", line 530, in __bootstrap_inner
    File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/threading.py", line 483, in run
    File "/Users/Wim/Bird Swarm/bird_swarm.py", line 156, in workerGuiThread
    self.root.after(200, self.workerGuiThread)
    File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-tk/Tkinter.py", line 501, in after
    File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-tk/Tkinter.py", line 1098, in _register
    RuntimeError: main thread is not in main loop

类:

class ThreadedClient(object):

    def __init__(self):
        self.queue = Queue.Queue( )
        self.gui = GuiPart(self.queue, self.endApplication)
        self.root = self.gui.getRoot()
        self.running = True
        self.GuiThread = threading.Thread(target=self.workerGuiThread) 
        self.GuiThread.start()

    def workerGuiThread(self):
        while self.running:
            self.root.after(200, self.workerGuiThread)
            self.gui.processIncoming( )     

    def endApplication(self): 
        self.running = False

    def tc_TekenVogel(self,vogel):
        self.queue.put(vogel)

class GuiPart(object):
    def __init__(self, queue, endCommand): 
        self.queue = queue
        self.root = Tkinter.Tk()
        Tkinter.Canvas(self.root,width=g_groottescherm,height=g_groottescherm).pack()
        Tkinter.Button(self.root, text="Move 1 tick", command=self.doSomething).pack()
        self.vogelcords = {} #register of bird and their corresponding coordinates 

    def getRoot(self):
        return self.root

    def doSomething():
        pass #button action

    def processIncoming(self):
        while self.queue.qsize( ):
            try:
                msg = self.queue.get(0)
                try:
                    vogel = msg
                    l = vogel.geeflocatie()
                    if self.vogelcords.has_key(vogel):
                        cirkel = self.vogelcords[vogel]
                        self.gcanvas.coords(cirkel,l.geefx()-g_groottevogel,l.geefy()-g_groottevogel,l.geefx()+g_groottevogel,l.geefy()+g_groottevogel)            
                    else:
                        cirkel = self.gcanvas.create_oval(l.geefx()-g_groottevogel,l.geefy()-g_groottevogel,l.geefx()+g_groottevogel,l.geefy()+g_groottevogel,fill='red',outline='black',width=1)
                        self.vogelcords[vogel] = cirkel 
                    self.gcanvas.update()
                except:
                    print('Failed, was van het type %' % type(msg))
            except Queue.Empty:
                pass

【问题讨论】:

  • 从您的回溯中,您似乎正在从您在其他地方创建的线程运行workerGuiThread,而不是从执行的主线程。我不是 TK 专家,但错误似乎表明这是不允许的(您需要使用主线程调用 TK 函数,如after)。
  • 请参阅this questionthis answer 等,了解在多线程程序中使用 TkInter 的一些详细信息。但简短的版本是:只在主线程中使用它,句号。
  • 嘿Blckknght。因此我正在使用 mtTkinter。

标签: python multithreading tkinter


【解决方案1】:

您正在主线程之外的线程中运行主 GUI 循环。你不能这样做。

文档在一些地方随便提到 Tkinter 不是完全线程安全的,但据我所知,从来没有完全站出来说你只能从主线程与 Tk 对话。原因是事实有些复杂。 Tkinter 本身线程安全的,但很难以多线程方式使用。最接近官方文档的似乎是this page

问。是否有线程安全的 Tkinter 替代方案?

Tkinter?

只需在主线程中运行所有 UI 代码,然后让编写者写入 Queue 对象……

(给出的示例代码不是很好,但足以弄清楚他们的建议并正确地做事。)

实际上 Tkinter 的线程安全替代品mtTkinter。它的文档实际上很好地解释了这种情况:

虽然 Tkinter 在技术上是线程安全的(假设 Tk 是使用 --enable-threads 构建的),但实际上在多线程 Python 应用程序中使用时仍然存在问题。问题源于 _tkinter 模块在处理来自其他线程的调用时试图通过轮询技术获得对主线程的控制。

我相信这正是您所看到的:您在 Thread-1 中的 Tkinter 代码正试图窥视主线程以找到主循环,但它不存在。

所以,这里有一些选项:

  • 按照 Tkinter 文档的建议执行并在主线程中使用 TkInter。可能通过将您当前的主线程代码移动到工作线程中。
  • 如果您正在使用其他一些想要接管主线程的库(例如,twisted),它可能具有与 Tkinter 集成的方法,在这种情况下您应该使用它。
  • 使用mkTkinter解决问题。

另外,虽然我没有找到与此问题完全相同的重复项,但在 SO 上有许多相关问题。有关详细信息,请参阅 this questionthis answer 等。

【讨论】:

  • 嘿阿巴纳特。感谢您的回答。我已经使用了 mtTkinter 的选项。我的代码正在运行(我的意思是没有错误)。但是我看不到画布......在日志记录中我看到程序正在运行......只是没有可视化。你看不到代码@github.com/wimhendrickx/Flocking/blob/master/bird_swarm.py。提前致谢。
  • @user2040823:为什么我看不到代码,它是白色背景上的白色文本吗? :) 反正我下载下来看看。
  • @user2040823:好的,这里有多个基本问题。首先,您不会在任何地方打电话给root.mainloop()。其次,你有像doSomething 这样的方法,它们不采用self(也不是staticmethods)。第三,您的 Tkinter 事件处理程序没有采用 event 参数。我认为在尝试围绕它构建复杂的东西和/或使用 mtTkinter 之前,您需要完成基本的 Tkinter 教程。如果您有任何无法找到答案的具体问题,请创建一个新问题,但我无法在 SO cmets 中教您 Tkinter 基础知识。
  • 你好。感谢查看我的代码。目前我只使用我的画布进行输出(按钮不起作用)。我只想想象成群中的鸟。但我会听从你的建议,建立一个小的子项目!
  • 作为对我自己和任何感兴趣的人的说明:当您不将队列用于 GUI 事件而只是从非主线程调用对象方法时会出现问题,该代码可能在 Linux 上工作,但在 Windows 上失败。或者可能在 py3.6 上工作并在 py3.5 上失败......
【解决方案2】:

由于所有这些确实帮助了我的问题,但并没有完全解决,这里还有一点需要记住:

就我而言,我开始在多个线程中导入 pyplot 库并在那里使用它。将所有库调用移动到我的主线程后,我仍然收到该错误。

我确实通过在其他线程中使用的其他文件中删除该库的所有导入语句来摆脱它。即使他们没有使用该库,也会导致同样的错误。

【讨论】:

  • 我想我也遇到了这个问题。将 Matplotlib 的所有导入移动到创建多处理池之后 就可以了,即使在池上运行的函数根本没有使用 matplotlib。干杯!
【解决方案3】:

我知道这已经很晚了,但我将我的线程设置为守护进程,并且没有引发异常:

t = threading.Thread(target=your_func)
t.setDaemon(True)
t.start()

【讨论】:

  • 这是为我修复它的唯一方法。后来我用t.root.quit() 停止它。
【解决方案4】:
from tkinter import *
from threading import Thread
from time import sleep
from random import randint

class GUI():

    def __init__(self):
        self.root = Tk()
        self.root.geometry("200x200")

        self.btn = Button(self.root,text="lauch")
        self.btn.pack(expand=True)

        self.btn.config(command=self.action)

    def run(self):
        self.root.mainloop()

    def add(self,string,buffer):
        while  self.txt:
            msg = str(randint(1,100))+string+"\n"
            self.txt.insert(END,msg)
            sleep(0.5)

    def reset_lbl(self):
        self.txt = None
        self.second.destroy()

    def action(self):
        self.second = Toplevel()
        self.second.geometry("100x100")
        self.txt = Text(self.second)
        self.txt.pack(expand=True,fill="both")

        self.t = Thread(target=self.add,args=("new",None))
        self.t.setDaemon(True)
        self.t.start()

        self.second.protocol("WM_DELETE_WINDOW",self.reset_lbl)

a = GUI()
a.run()

也许这个例子会对某人有所帮助。

【讨论】:

  • 本网站不鼓励仅使用代码的答案。如果您解释您所做的与原始代码不同,这个答案会更好。
【解决方案5】:

写在最后:

root.mainloop()

当然,如果不是root,则root 应该是您的Tk 对象的名称。

【讨论】:

  • 当它解决了我的问题时,我正要对此投反对票。只需几个配置!!!
【解决方案6】:

我找到了解决它的方法。 它可能看起来像个笑话,但你应该添加

plt.switch_backend('agg')

【讨论】:

  • 没有任何迹象表明作者使用了pyplot。
  • 如果您想显示图像,这似乎也不起作用:“用户警告:Matplotlib 当前正在使用 agg,这是一个非 GUI 后端,因此无法显示图形。”
猜你喜欢
  • 2019-03-21
  • 1970-01-01
  • 1970-01-01
  • 2018-09-29
  • 1970-01-01
  • 1970-01-01
  • 2022-06-16
  • 1970-01-01
相关资源
最近更新 更多