RuntimeError: main thread is not in main loop
Asked Answered
F

10

58

When I call

self.client = ThreadedClient() 

in my Python program, I get the error

"RuntimeError: main thread is not in main loop"

I have already done some googling, but I am making an error somehow ... Can someone please help me out?

Full error:

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

Classes:

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
Fullgrown answered 4/2, 2013 at 19:51 Comment(3)
From your traceback, it looks like you're running the workerGuiThread from a thread you're creating elsewhere, rather from the main thread of execution. I'm not a TK expert, but the error seems to suggest that this is not allowed (you need to use the main thread to call the TK functions, like after).Barogram
See this question, this answer, etc. for some details on using TkInter in a multithreaded program. But the short version is: Only use it in the main thread, period.Syce
Hey Blckknght. Therefor I am using mtTkinter.Fullgrown
S
62

You're running your main GUI loop in a thread besides the main thread. You cannot do this.

The docs mention offhandedly in a few places that Tkinter is not quite thread safe, but as far as I know, never quite come out and say that you can only talk to Tk from the main thread. The reason is that the truth is somewhat complicated. Tkinter itself is thread-safe, but it's hard to use in a multithreaded way. The closest to official documentation on this seems to be this page:

Q. Is there an alternative to Tkinter that is thread safe?

Tkinter?

Just run all UI code in the main thread, and let the writers write to a Queue object…

(The sample code given isn't great, but it's enough to figure out what they're suggesting and do things properly.)

There actually is a thread-safe alternative to Tkinter, mtTkinter. And its docs actually explain the situation pretty well:

Although Tkinter is technically thread-safe (assuming Tk is built with --enable-threads), practically speaking there are still problems when used in multithreaded Python applications. The problems stem from the fact that the _tkinter module attempts to gain control of the main thread via a polling technique when processing calls from other threads.

I believe this is exactly what you're seeing: your Tkinter code in Thread-1 is trying to peek into the main thread to find the main loop, and it's not there.

So, here are some options:

  • Do what the Tkinter docs recommend and use TkInter from the main thread. Possibly by moving your current main thread code into a worker thread.
  • If you're using some other library that wants to take over the main thread (e.g., twisted), it may have a way to integrate with Tkinter, in which case you should use that.
  • Use mkTkinter to solve the problem.

Also, while I didn't find any exact duplicates of this question, there are a number of related questions on SO. See this question, this answer, and many more for more information.

Syce answered 4/2, 2013 at 20:26 Comment(6)
Hey abarnert. Thanks for your answer. I have used for the option with mtTkinter. My code is running (without errors I mean). But I can't see the canvas ... In the logging I see the program is working ... only without visualisation. You can't see the code @ github.com/wimhendrickx/Flocking/blob/master/bird_swarm.py. Thanks in advance.Fullgrown
@user2040823: Why can't I see the code, is it in white text on a white background? :) Anyway, I'll download it and take a look.Syce
@user2040823: OK, there are multiple elementary problems here. First, you're not calling root.mainloop() anywhere. Second, you've got methods like doSomething that don't take self (and aren't staticmethods). Third, your Tkinter event handlers aren't taking an event parameter. I think you need to work through a basic Tkinter tutorial before you try to build something complicated around it and/or use mtTkinter. If you have any specific questions that you can't find answers to, create a new question, but I can't teach you Tkinter basics in SO comments.Syce
Hey again. Thx for looking at my code. At the moment I am only using my canvas for output (the button is not working). I just want to visualize the birds in the swarm. But I shall take your advice and build a small sub project!Fullgrown
As a note to myself and anybody interested: there is a problem when you don't use queue for GUI events and instead just call object methods from non-main thread, the code might work on Linux but fail on Windows. Or probably work on py3.6 and fail on py3.5...Wintery
Question: How can the main thread call after() while it is blocked doing mainloop()? More details: Imagine your frontend receives an API response. Your worker thread nicely adds the task into the queue. However, without any event, your main thread cannot stop to read the queue! Unless you use after every 10ms to poll? But that is not elegant.Tragus
M
31

I found a way to solve it. it might look like a joke but you just should add

plt.switch_backend('agg')
Makhachkala answered 31/10, 2020 at 12:59 Comment(7)
There is nothing that suggests that the author uses pyplot.Tributary
Also this does not seem to work if you want to display the images: "UserWarning: Matplotlib is currently using agg, which is a non-GUI backend, so cannot show the figure."Ashkhabad
This worked for me, I just 'exported' the png using savefig(), hth.Cathern
Thanks man! It solves my problem with py + kedro + ml_flow -> MlflowArtifactDataSet It occurs on Windows only - the e2e test in an docker container with the same code pass without this error. DataSetError: Failed while saving data to data set MlflowMatplotlibWriter(filepath=C:/Users/path_to_project/data/08_reporting/naive_recommendations.png, protocol=file, save_args={}). main thread is not in main loopClosefisted
You can also try 'WebAgg'. This will open up figures in a browser, which might not be what you want, but at least it avoids this.Electrodynamic
it works like a charm for sure for those that use matplotlib.pyplot (plt( in the main loop.Brisesoleil
What's the default backend? What's agg? A little reference would be nice.Vastah
P
24

I know this is late, but I set my thread to a Daemon, and no exception was raised:

t = threading.Thread(target=your_func)
t.setDaemon(True)
t.start()
Pyretic answered 1/12, 2019 at 21:59 Comment(1)
This was the only method that fixed it for me. I later stop it with t.root.quit().Luzern
S
4

Since all this did help my problem but did not solve it completely here is an additional thing to keep in mind:

In my case I started off importing the pyplot library in many threads and using it there. After moving all the library calls to my main thread I still got that error.

I did get rid of it by removing all import statements of that library in other files used in other threads. Even if they did not use the library the same error was caused by it.

Siegler answered 30/12, 2018 at 23:24 Comment(1)
I guess I was running into this as well. Moving all the imports for Matplotlib to after the multiprocessing pool creation did the trick, even though the functions running on the pool did not use matplotlib at all. Cheers!Weakfish
C
4

Write it at the end:

root.mainloop()

Of course, in place of root should be the name of your Tk object if it is not root.

Calcareous answered 1/7, 2020 at 20:56 Comment(2)
I was about to downvote this when it solved my problem. Just a few configurations!!!Revis
Don't forget to upvote this!Peruzzi
L
3
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()

maybe this example would help someone.

Landy answered 28/4, 2020 at 20:40 Comment(1)
code-only answers are discouraged on this site. This answer would be better if you explained what you're doing that is different from the original code.Joslin
W
2

Installing the improved version of tkinter can solve this problem. No need to modify your code. You need only do following: pip3 install mtTkinter

then add following in your code: from mttkinter import mtTkinter as tk

Witchy answered 6/3, 2023 at 5:53 Comment(0)
S
1

You cannot modify your main GIU from another thread you need to send event to the main GUI in order to avoid exceptions Use window.write_event_value instead, this method allows you to send events from your threads you can take a look a this too: window.perform_long_operation

Supersession answered 19/5, 2022 at 15:28 Comment(0)
T
0

I know this question was asked a long time ago, but I wanted to tell you how I solved it. In my case, I have a program that sends and receives messages through the serial port and uses the TKinter library.

If I do:

while (True):
    #more code here
    window.update_idletasks()
    window.update()

The code crashes when a thread tries to access a tkinter function. But, if I do this:

window.mainloop()

All the threads execute normaly. Hope this helps someone.

Teodoor answered 16/11, 2022 at 22:38 Comment(0)
M
0

Put a try block around any root.quit() or root.destroy() calls and catch the RuntimeException. This works like a charm for me. Also, join the thread after quit/destroy.

I also use a closing flag that can be manipulated by either thread. It makes sure that the quit/destroy calls are made in the main thread. Basically the thread can set the flag to True and the main thread needs to check the flag. Something like this (although I use this in a class and there the flag, window and thread are attributes):

window = None
thread = threading.Thread(target=run_window)
thread.start()

def run_window():
    global window
    window = tk.Tk()
    window.mainloop()

def destroy_window():
    try:
        window.quit()
    except RuntimeError:
        pass
    try:
        window.destroy()
    except RuntimeError:
        pass
    thread.join()
Mande answered 16/3 at 15:47 Comment(2)
Also, absolutely make sure that you set the master of every widget created within the thread to the window that is created in that thread. (This also goes for e.g. tk.PhotoImage(master=window, file=<yourimagepath>))Mande
Welcome to StackOverflow, and thank you for a well-written answer! Since the current top-rated answer is both very mature and highly rated, is it possible to relate your contribution to it in any way? Are you filling in something they missed? Always feel free to edit your post if you discover you omitted important information.Cadency

© 2022 - 2024 — McMap. All rights reserved.