Mainloop and Text with threads

一曲冷凌霜 提交于 2019-12-11 07:59:42

问题


I have threads that use some class's functions , and those functions print alot of stuff , that i want to display on a Text() widget .

So i tried making the window in the class as a class variable and the command : mainloop() seems to stop everything from continuing ....

Is there any solution for that ?

The general idea i want to do : (converting the console to GUI..)

from tkinter import *


root = Tk()
textbox = Text(root)
textbox.pack()

def redirector(inputStr):
    textbox.insert(INSERT, inputStr)

sys.stdout.write = redirector
root.mainloop()

the whole code :

import threading
from queue import Queue
from Spider import Spider
from domain import *
from general import *
from tkinter import *



def mmm(answer1,answer2,master):  # answer1,answer2 are user inputs from the first GUI that gets info, master is the root so i can close it

    master.destroy()
    PROJECT_NAME = answer1
    HOMEPAGE = answer2
    DOMAIN_NAME = get_domain_name(HOMEPAGE)
    QUEUE_FILE = PROJECT_NAME + '/queue.txt'
    CRAWLED_FILE = PROJECT_NAME + '/crawled.txt'
    NUMBER_OF_THREADS = 8

    queue = Queue()  # thread queue
    Spider(PROJECT_NAME, HOMEPAGE, DOMAIN_NAME) # a class where the prints happen and some other functions.

    root = Tk()
    textbox = Text(root)
    textbox.pack()

    def redirector(inputStr):
        textbox.insert(INSERT, inputStr)

    sys.stdout.write = redirector
    root.mainloop()
    # create threads (will die when exit)
    def create_threads():
        for x in range(NUMBER_OF_THREADS):
            t = threading.Thread(target=work)
            t.daemon = True
            t.start()


    # do the next link in the queue
    def work():
        while True:
            url = queue.get()
            Spider.crawl_page(threading.current_thread().name, url)
            queue.task_done()


    # each link is a new job
    def create_jobs():
        for link in file_to_set(QUEUE_FILE):
            queue.put(link)  # put the link in the thread queue
        queue.join()  # block until all processed
        crawl()


    # if there are items in the queue, crawl them
    def crawl():
        queued_links = file_to_set(QUEUE_FILE)
        if len(queued_links) > 0:
            print(str(len(queued_links)) + ' links in the queue')
            create_jobs()


    create_threads()
    crawl()

回答1:


As soon as you start the mainloop(), you get an event driven app that runs in a loop. Any code that is placed after the line root.mainloop() will run only after the GUI is terminated. It is expected that your GUI is more or less self contained. You populate it with tkinter widgets that will have some events bound to them, each event with its proper callback function.

Be aware, however, that tkinter is not thread safe. You will need to separate very well the theading code, ensuring that it does not call any GUI widgets, for instance. In this page you can find a Python2 example on how to do threading with tkinter.

But maybe you don't even need threads. You can for instance schedule a function to run every X seconds with after(), wich may read an updated log file or get updated values from a database, and update the GUI accordingly. You can find some examples and explanation in this page.




回答2:


A @Victor Domingos's mentions are really usefull in your case, but your real problem - your own code! First of all - take a look at structure of your application and understand, that it's weak, no offence (you even pass a master to a function to destroy it). So I suggest you to read about classes and inheritance in Python (if you don't already) and then take a look here.

Next stop - your redirector. You reassign sys.stdout.write, but you never preserve it - so it another weak spot. Ok, let's say that now you preserve it, but if we keeping object oriented approach - I would prefer this option.

Also, is it really necessary to destroy the master? For output you can use a Toplevel widget if you destroing master just to avoid two mainloop's. You can even hide root while Toplevel is active. Marvelous, isn't it?

Finally, to answer your question about solution. There're no straight solution, but only one: read and try. You're already answered why mainloop stops everything, but your question is really broad.

I tried to reproduce your full program (2-window app, 1st-user input, 2nd - console-like and some example printing task with thread) and here's a code:

# imports:
try:
    import tkinter as tk
except ImportError:
    import Tkinter as tk

import sys
import string
import random
import threading


# classes:
class ReStdout:
    # common stdout-redirector
    def __init__(self, target_widget, start_redirection=True):
        self.text_console = target_widget
        if start_redirection:
            self.start_redirection()

    def __del__(self):
        self.stop_redirection()

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.stop_redirection()

    def __enter__(self):
        pass

    def flush(self):
        pass

    def write(self, stdout_line):
        self.text_console.insert('1.0', stdout_line)

    def start_redirection(self):
        sys.stdout = self

    @staticmethod
    def stop_redirection():
        sys.stdout = sys.__stdout__


class App(tk.Tk):
    # common tk app
    def __init__(self):
        tk.Tk.__init__(self)
        self.resizable(width=True, height=False)
        self.minsize(width=250, height=25)
        self.some_entry = tk.Entry(self)
        self.some_entry.insert(0, 'You can pass something to Spawner!')
        self.some_entry.pack(expand=True, fill='x')
        self.start_spawner_button = tk.Button(self, text='Start Spawner', command=self.spawn_spawner)
        self.start_spawner_button.pack(expand=True, fill='x')

    def spawn_spawner(self):
        Spawner(self, self.some_entry.get())

    def close_app(self):
        self.destroy()


class Spawner(tk.Toplevel):
    # common tk app - task spawner
    def __init__(self, master, entry_string):
        tk.Toplevel.__init__(self, master)
        self.resizable(width=False, height=False)
        self.preserved_count = threading.active_count()
        self.master = master
        self.master.withdraw()

        self.spawn_task_button = tk.Button(self, text='Spawn Task', command=spawn_task)
        self.spawn_task_button.pack(expand=True, fill='x')

        self.quit_button = tk.Button(self, text='Quit', command=self.close_app)
        self.quit_button.pack(expand=True, fill='x')

        self.text = tk.Text(self, bg='black', fg='white')
        self.text.pack(expand=True, fill='both')
        self.stdout = ReStdout(self.text)
        self.protocol('WM_DELETE_WINDOW', self.close_app)

        # print what have been passed
        print('Users Input: %s' % entry_string)

        # let's spawn something right now
        # after here just for example
        # try to use it w/o after
        self.after(500, multi_spawn)

    def close_app(self):
        if threading.active_count() == self.preserved_count:
            self.stdout.stop_redirection()
            self.master.deiconify()
            self.destroy()
        else:
            # code to handle threads
            print('\n**** Cant quit right now! ****\n')


# non - class functions:
def multi_spawn(count=1):
    for _ in range(count):
        spawn_task()


def spawn_task():
    task_count = threading.active_count()
    task = threading.Thread(target=lambda: common_task(comment='%d printing task' % task_count,
                                                       iteration_count=random.randint(1, 10)))
    task.start()


def common_task(comment, iteration_count=1):
    # example task wait + print
    task_numb = comment.split(None, 1)[0]
    print('\nTask spawned:\n%s\nIteration count: %d\n' % (comment, iteration_count))

    for _ in range(iteration_count):
        threading.Event().wait(1)
        print('Task: %s \t Iteration: %d \t Generated: %s' % (task_numb, _ + 1, generate_smth()))

    print('\nTask %s completed!' % task_numb)


def generate_smth(size=6, chars=string.ascii_uppercase + string.digits):
    # generate random
    return ''.join(random.choice(chars) for _ in range(size))

# entry-point:
print('Just another example from SO')
app = App()
app.mainloop()
print('Beep')

As you see - I never get stucked (when I don't needed it) in mainloop, because I create threads on events: __init__ of "Spawner" (thanks to inheritance) and a button click event. Of course, it's just one approach from many, but I wish that now your problem is clearer to you.



来源:https://stackoverflow.com/questions/43781334/mainloop-and-text-with-threads

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!