运行时错误:主线程不在主循环中

56 投票
10 回答
172357 浏览
提问于 2025-04-17 14:44

当我在我的Python程序中调用

self.client = ThreadedClient() 

时,我遇到了一个错误

"运行时错误:主线程不在主循环中"

我已经在网上查了一些资料,但我还是搞错了... 有人能帮我一下吗?

完整错误信息:

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

10 个回答

24

我知道这个回答来得有点晚,但我把我的线程设置成了守护线程,结果没有出现任何异常:

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

我找到了解决这个问题的方法。听起来可能像是在开玩笑,但你只需要添加以下内容:

plt.switch_backend('agg')
61

你在一个线程中运行主界面循环,而不是在主线程里。这样做是不行的。

文档中提到过几次,Tkinter并不是完全安全的多线程使用,但据我所知,从来没有明确说过只能在主线程中与Tk进行交互。原因有点复杂。Tkinter本身是可以在多线程中使用的,但实际上要在多线程环境下使用它会比较困难。关于这个问题的官方文档似乎可以参考这一页

问:有没有线程安全的Tkinter替代品?

答:Tkinter?

只需在主线程中运行所有的用户界面代码,并让其他线程通过一个队列对象来进行写入……

(虽然给出的示例代码不是很好,但足以让你明白他们的建议,并正确地进行操作。)

实际上,确实有一个线程安全的Tkinter替代品,叫做mtTkinter。它的文档对这个情况解释得相当清楚:

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

我相信这正是你所遇到的问题:你的Tkinter代码在线程1中试图查看主线程,以找到主循环,但它并不在那里。

所以,这里有几个选项:

  • 按照Tkinter文档的建议,在主线程中使用Tkinter。可以考虑将你当前的主线程代码移到一个工作线程中。
  • 如果你在使用其他想要接管主线程的库(例如twisted),它可能有与Tkinter集成的方法,这种情况下你应该使用那个。
  • 使用mkTkinter来解决这个问题。

另外,虽然我没有找到这个问题的确切重复,但在Stack Overflow上有很多相关的问题。可以查看这个问题这个答案,以及更多的信息。

撰写回答