Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.4k views
in Technique[技术] by (71.8m points)

python - How to handle errors in tkinter mainloop?

I have a python program which is scraping web data for a client. tkinter is used for the interface. Outline is:

  1. Window 1 lets the user select what information to scrape.
  2. Window 1 closes
  3. Separate thread is started for the scraper. This thread will in turn spawn more threads to allow multiple pages to be downloaded at once.
  4. Window 2 opens to show download progress (e.g. "downloading client 5 of 17")
  5. User closes Window 2 to end program.

The program will work for the first few hundred pages, but then it starts spitting out the error message:

Traceback (most recent call last):
File "C:UsersMeAppDataLocalProgramsPythonPython35-32libkinter\__init__.py", line 248, in __del__
if self._tk.getboolean(self._tk.call("info", "exists", self._name)):
RuntimeError: main thread is not in main loop
Exception ignored in: <bound method Variable.__del__ of <tkinter.IntVar object at 0x03245510>>

multiple times until all the threads have been stopped. No idea what could be causing this error. The actual code is:

import scraper, threading
import tkinter as tk
from queue import Queue

outputQueue = Queue()

class OutputRedirect(object):
    def __init__():
        super().__init__()

    def write(self, string):
        outputQueue.put(string)

def getInformation():
    stdout = sys.stdout
    sys.stdout = OutputRedirect()

    scraper.startThreads()
    scraper.startPulling() 

    sys.stdout = stdout

def updateTextField(window, root):

    if not outputQueue.empty():
        string = outputQueue.get()
        window.textArea.insert("insert", string)         
        outputQueue.task_done()

    root.after(1, updateTextField, window, root)

'''widget/window definitions - not important'''

guiInfo = {"stuff1": [], "stuff2": []}

root = tk.Tk()
window1 = Window1(root, guiInfo)
window1.mainloop()

pullThread = threading.Thread(target=pullClaims,
                              args=(list(guiInfo["stuff1"]),
                              list(guiInfo["stuff2"])), daemon=True)
pullThread.start()

root = tk.Tk()
window2 = Window2(root)
root.after(0, updateTextField, window2, root)
window2.mainloop()

The scraper program (which works fine on its own) uses print statements for user feedback. Rather than re-write everything, I just pointed stdout to a queue. The main thread uses the "after" function to check on the queue a few times a second. If there is anything in it then it gets printed to the Text widget on the window.

I've put try/catch just about everywhere in the code, but they haven't caught a thing. I'm convinced the problem is in the mainloop itself, but I can't find any up to date information for how to stick something new in it. Any help would be greatly appreciated.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

To handle tkinter errors you do the following

class TkErrorCatcher:

    '''
    In some cases tkinter will only print the traceback.
    Enables the program to catch tkinter errors normally

    To use
    import tkinter
    tkinter.CallWrapper = TkErrorCatcher
    '''

    def __init__(self, func, subst, widget):
        self.func = func
        self.subst = subst
        self.widget = widget

    def __call__(self, *args):
        try:
            if self.subst:
                args = self.subst(*args)
            return self.func(*args)
        except SystemExit as msg:
            raise SystemExit(msg)
        except Exception as err:
            raise err

import tkinter
tkinter.CallWrapper = TkErrorCatcher

But in your case please do not do this. This should only ever be done in the case of wanting to hide error messages from your users in production time. As commented above you have a nono's going on. To spawn multiple windows you can use tkinter.Toplevel

I would recommend in general to read

and for your specific problem of threading in tkinter this blog post nails it. Basically you need to have the tkinter mainloop blocking the programs main thread, then use after calls from other threads to run other code in the mainloop.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

2.1m questions

2.1m answers

60 comments

57.0k users

...