【问题标题】:Mainloop and Text with threads主循环和带线程的文本
【发布时间】:2017-10-02 13:00:11
【问题描述】:

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

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

有什么解决办法吗?

我想做的一般想法:(将控制台转换为 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()

【问题讨论】:

    标签: python python-3.x user-interface tkinter tk


    【解决方案1】:

    启动mainloop() 后,您将获得一个循环运行的事件驱动应用程序。放置在 root.mainloop() 行之后的任何代码将仅在 GUI 终止后运行。预计您的 GUI 或多或少是独立的。您使用 tkinter 小部件填充它,这些小部件将绑定一些事件,每个事件都有其适当的回调函数。

    但是请注意,tkinter 不是线程安全的。例如,您需要很好地分离主题代码,确保它不会调用任何 GUI 小部件。在this page 中,您可以找到一个 Python2 示例,了解如何使用 tkinter 进行线程处理。

    但也许你甚至不需要线程。例如,您可以使用after() 安排一个函数每 X 秒运行一次,它可以读取更新的日志文件或从数据库获取更新的值,并相应地更新 GUI。你可以在this page找到一些例子和解释。

    【讨论】:

    • ye 所以我得到了这个错误,它需要在主线程中......这是什么意思?
    • 我无法使用您提供的代码示例在此处重现您的问题...它运行并在 tkinter 窗口中显示一个空文本小部件。我相信它的行为就像你编码的那样。现在,如果您想要一个文本小部件实时显示控制台中显示的任何内容,并对用户输入做出反应,这将是一项复杂得多的任务。我不确定是否可能,它可能有一些限制。
    • 是的,我的错我没有链接我的主要代码....这只是示例,我将在 1 秒内编辑它
    • 您将sys.stdout.write 重新分配为函数redirector,但它永远不会在上面的代码中被调用。但是,这样做,恐怕会干扰tkinter。我认为您不能从不同的线程直接调用小部件中的方法。将输出写入文件然后让另一个任务监视最近的文件大小更改会更安全,也可能更容易。这样,您甚至可以通过使用 2 个独立的应用程序,运行两个不同的 Python 实例(一个写入文件,另一个使用 GUI 显示它定期自我更新)来实现。
    • 所以基本上是让线程写入文件并同时将不同的线程放在屏幕上?我会同时写作和阅读有任何问题吗?以及如何避免主线程的错误?
    【解决方案2】:

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

    下一站 - 您的重定向器。你重新分配了sys.stdout.write,但你从来没有保留它——所以它是另一个弱点。好的,假设现在您保留它,但如果我们保持面向对象的方法 - 我更喜欢 this 选项。

    另外,destroy master 真的有必要吗?对于输出,如果您销毁master 只是为了避免两个mainloop,则可以使用Toplevel 小部件。 You can even hide root while Toplevel is active。太棒了,不是吗?

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

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

    # 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 中(当我不需要它时),因为我在事件上创建线程:“Spawner”的 __init__(感谢继承)和按钮单击事件。当然,这只是众多方法中的一种,但我希望现在你的问题对你来说更清楚了。

    【讨论】:

    • 非常感谢您的评论 :) 您所说的 TopLevel 应用程序是什么意思?类有什么用? ,我真的不明白为什么......我破坏了主人,所以我关闭了......
    • 'Toplevel' 只是另一个 tkinter 小部件,如按钮或条目。 There're good material to tkinter in general。你关于课程的问题也很广泛。但这里有一个关于它的another link。如果它看起来像你想要的,请随意重写我的代码!
    • 我了解你的代码,但我真的很难使用它,因为它与我正在做的事情并不接近......你能告诉我要删除什么以及将我自己的代码放在哪里吗?因为我喜欢迷失在这段代码中
    猜你喜欢
    • 2019-03-21
    • 1970-01-01
    • 1970-01-01
    • 2019-03-09
    • 2018-09-29
    • 1970-01-01
    • 2013-01-19
    • 1970-01-01
    相关资源
    最近更新 更多