Mainloop 和带有线程的文本

问题描述:

我有使用某些类函数的线程,这些函数打印了很多我想在 Text() 小部件上显示的东西.

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 .

所以我尝试将类中的窗口作为类变量,命令:mainloop() 似乎阻止了一切继续......

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

有什么解决办法吗?

我想做的一般想法:(将控制台转换为 GUI ..)

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()

整个代码:

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()

@Victor Domingos 的提及对您的情况非常有用,但您的真正问题是您自己的代码!首先 - 看看你的应用程序的结构并理解它很弱,没有冒犯性(你甚至将 master 传递给一个函数来 destroy 它).所以我建议你阅读 Python 中的类和继承(如果你还没有),然后看看 此处.

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.

下一站 - 您的重定向器.您重新分配 sys.stdout.write,但您从未保留它 - 因此它又是另一个弱点.好吧,假设现在你保留它,但如果我们保持面向对象的方法 - 我更喜欢 这个 选项.

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.

另外,真的有必要摧毁master吗?对于输出,如果您销毁 master 只是为了避免两个 mainloop,则可以使用 Toplevel 小部件.你可以甚至在 Toplevel 处于活动状态时隐藏 root.太棒了,不是吗?

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?

最后,回答您关于解决方案的问题.没有直接的解决方案,只有一个:阅读并尝试.您已经回答了为什么 mainloop 停止一切,但您的问题非常广泛.

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.

我尝试重现您的完整程序(2 个窗口应用程序、第一个用户输入、第二个 - 类似控制台的和一些带有线程的示例打印任务),这是一个代码:

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')

如您所见 - 我从不卡在 mainloop 中(当我不需要它时),因为我在事件上创建线程:__init__ of "Spawner" (感谢继承)和一个按钮点击事件.当然,这只是许多方法中的一种,但我希望现在你的问题对你来说更清楚了.

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.