简体   繁体   English

如何防止 tkinter Gui 在异步任务运行时冻结?

[英]How can I prevent a tkinter Gui from freezing while an async task is running?

I want to create a non blocking Gui with tkinter.我想用 tkinter 创建一个非阻塞的 Gui。 The way I have seen it so far, you can do as with a mutliprocess.到目前为止我所看到的方式,您可以像使用多进程一样进行操作。 But now I have the problem that I want to access the mainloop of the gui again with the newly created thread and I always get an error here.但是现在我有一个问题,我想用新创建的线程再次访问 gui 的主循环,我总是在这里得到一个错误。 can you jump back and forth between two threads or is there another method to not block the Gui?你可以在两个线程之间来回跳转还是有另一种不阻塞 Gui 的方法?

import asyncio
import tkinter as tk 
import multiprocessing as mp 

class pseudo_example():


    def app(self):
        self.root = tk.Tk()
        self.root.minsize(100,100)

        start_button = tk.Button(self.root, text="start", command=lambda: mp.Process(target=self.create_await_fun).start())
        start_button.pack()  #

        self.testfield = tk.Label(self.root, text="test")
        self.testfield.pack()

        #self.root.update_idletasks()
        self.root.mainloop()

    def create_await_fun(self):
        asyncio.run(self.await_fun())

    async def await_fun(self):
        self.root.update_idletasks()
        self.testfield["text"] = "start waiting"
        await asyncio.sleep(2)
        self.testfield["text"] = "end waiting"



if __name__ == '__main__':
    try:
        gui = pseudo_example()
        gui.app()
    except KeyboardInterrupt:
        print("Interrupted")
        sys.exit()

Error message:错误信息:

[xcb] Unknown sequence number while processing queue [xcb] Most likely this is a multi-threaded client and XInitThreads has not been called [xcb] Aborting, sorry about that. [xcb] 处理队列时序列号未知 [xcb] 这很可能是一个多线程客户端,并且 XInitThreads 尚未被调用 [xcb] 正在中止,对此感到抱歉。 XIO: fatal IO error 0 (Success) on X server ":0" after 401 requests (401 known processed) with 0 events remaining. XIO:401 个请求(401 个已知处理)后 X 服务器“:0”上的致命 IO 错误 0(成功),剩余 0 个事件。 python3.8: ../../src/xcb_io.c:259: poll_for_event: Assertion `!xcb_xlib_threads_sequence_lost' failed. python3.8:../../src/xcb_io.c:259:poll_for_event:断言'!xcb_xlib_threads_sequence_lost'失败。

i know that the after() method exists but i don't know how to use it with asyncio without starting the asyncio task.我知道 after() 方法存在,但我不知道如何在不启动 asyncio 任务的情况下将它与 asyncio 一起使用。 Asyncio is unnecessary in the minimal example but I need it for another application. Asyncio 在最小示例中是不必要的,但我需要它用于另一个应用程序。

Tkinter doesn't support multi-task/multithreading. Tkinter 不支持多任务/多线程。 You could use mtTkinter which provides thread safety when using multi-tasking: https://pypi.org/project/mttkinter/您可以使用 mtTkinter,它在使用多任务时提供线程安全: https ://pypi.org/project/mttkinter/

You could also use a queue system to transfur data between two functions however you can't do anything with tkinter objects that cross between the two threads.您也可以使用队列系统在两个函数之间传输数据,但是您不能对跨两个线程的 tkinter 对象执行任何操作。 Using Queue with tkinter (and threading) 将队列与 tkinter(和线程)一起使用

Idk whether this helps.不知道这是否有帮助。 Probably better than using asyncio.可能比使用 asyncio 更好。

ItzTheDodo. ItzTheDodo。

An example with async-tkinter-loop library (written by me): async-tkinter-loop库的示例(由我编写):

import asyncio
import tkinter as tk 
import sys

from async_tkinter_loop import async_handler, async_mainloop


class pseudo_example():
    def app(self):
        self.root = tk.Tk()
        self.root.minsize(100,100)

        start_button = tk.Button(self.root, text="start", command=async_handler(self.await_fun))
        start_button.pack()

        self.testfield = tk.Label(self.root, text="test")
        self.testfield.pack()

        async_mainloop(self.root)

    async def await_fun(self):
        self.testfield["text"] = "start waiting"
        await asyncio.sleep(2)
        self.testfield["text"] = "end waiting"


if __name__ == '__main__':
    gui = pseudo_example()
    gui.app()

I usually use queues, I place you a sample.In the mask you will see on the status bar change the time, which is managed by a separate thread and by consulting the thread tail, see the class Clock, while you can use the various buttons without the mask freezing.I think you can take inspiration from this example.我通常使用队列,我给你放一个示例。在掩码中,你会在状态栏上看到更改时间,这是由单独的线程管理的,通过查询线程尾部,请参阅时钟类,同时你可以使用各种没有面具冻结的按钮。我想你可以从这个例子中获得灵感。

#!/usr/bin/python3
import sys
import threading
import queue
import datetime
import time
import tkinter as tk
from tkinter import ttk
from tkinter import messagebox

class Clock(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)

        self.queue = queue.Queue()
        self.check = True

    def stop(self):
        self.check = False

    def run(self):

        """Feeds the tail."""

        while self.check:
            s = "Astral date: "
            t = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
            msg = "{0} {1}".format(s, t)
            time.sleep(1)
            self.queue.put(msg)
    
    def check_queue(self, obj):

        """Returns a formatted string representing time.
           obj in this case is the statusbar text"""

        while self.queue.qsize():
            try:
                x = self.queue.get(0)
                msg = "{0}".format(x)
                obj.set(msg)
            except queue.Empty:
                pass
            

class Main(ttk.Frame):
    def __init__(self, parent, ):
        super().__init__(name="main")

        self.parent = parent
        self.text = tk.StringVar()
        self.spins = tk.IntVar()
        self.option = tk.IntVar()
        self.check = tk.BooleanVar()
        self.values = ('Apple','Banana','Orange')
        self.status_bar_text = tk.StringVar()
        self.init_status_bar()
        self.init_ui()

    def init_status_bar(self):

        self.status = tk.Label(self,
                               textvariable=self.status_bar_text,
                               bd=1,
                               relief=tk.SUNKEN,
                               anchor=tk.W)
        self.status.pack(side=tk.BOTTOM, fill=tk.X)        
          
    def init_ui(self):

        f0 = ttk.Frame(self)
        f1 = ttk.Frame(f0,)

        ttk.Label(f1, text = "Combobox").pack()
        self.cbCombo = ttk.Combobox(f1,state='readonly',values=self.values)
        self.cbCombo.pack()
        
        ttk.Label(f1, text = "Entry").pack()
        self.txTest = ttk.Entry(f1, textvariable=self.text).pack()

        ttk.Label(f1, text = "Spinbox").pack()
        tk.Spinbox(f1, from_=0, to=15, textvariable= self.spins).pack()

        ttk.Label(f1, text="Checkbutton:").pack()
        ttk.Checkbutton(f1,
                       onvalue=1,
                       offvalue=0,
                       variable=self.check).pack()


        ttk.Label(f1, text="Radiobutton:").pack()
        for index, text in enumerate(self.values):
            ttk.Radiobutton(f1,
                            text=text,
                            variable=self.option,
                            value=index,).pack()
            

        ttk.Label(f1, text="Listbox:").pack()
        self.ListBox = tk.Listbox(f1)
        self.ListBox.pack()
        self.ListBox.bind("<<ListboxSelect>>", self.on_listbox_select)
        self.ListBox.bind("<Double-Button-1>", self.on_listbox_double_button)
        
              
        f2 = ttk.Frame(f0,)

        bts = [("Callback", 7, self.on_callback, "<Alt-k>"),
               ("Args", 0, self.on_args, "<Alt-a>"),
               ("kwargs", 1, self.on_kwargs, "<Alt-w>"),
               ("Set", 0, self.on_set, "<Alt-s>"),
               ("Reset", 0, self.on_reset, "<Alt-r>"),
               ("Close", 0, self.on_close, "<Alt-c>")]

        for btn in bts:
            ttk.Button(f2,
                       text=btn[0],
                       underline=btn[1],
                       command = btn[2]).pack(fill=tk.X, padx=5, pady=5)
            self.parent.bind(btn[3], btn[2])
            
        f1.pack(side=tk.LEFT, fill=tk.BOTH, expand=1)
        f2.pack(side=tk.RIGHT, fill=tk.Y, expand=0)
        f0.pack(fill=tk.BOTH, expand=1)

    def on_open(self):
        self.periodic_call()
        
    def on_callback(self, evt=None):

        print ("self.cbCombo = {}".format(self.cbCombo.get()))
        print ("self.text = {}".format(self.text.get()))
        print ("self.spins = {}".format(self.spins.get()))
        print ("self.check = {}".format(self.check.get()))
        print ("self.option = {}".format(self.option.get()))
        if self.ListBox.curselection():
            print("ListBox.curselection = {}".format(self.ListBox.curselection()[0]))
        else:
            print("{0}".format("No selected item on listbox"))

    def on_args(self, evt=None):

        print("args type: {}".format(type(self.master.args)))
        for p, i in enumerate(self.master.args):
            print(p, i)

    def on_kwargs(self, evt=None):

        print("kwargs type: {}".format(type(self.master.kwargs)))
        for k, v in self.master.kwargs.items():
            print("{0}:{1}".format(k,v))

    def on_reset(self, evt=None):
        self.text.set('')
        self.spins.set(0)
        self.check.set(0)

    def on_set(self, evt=None):
        self.cbCombo.current(1)
        self.text.set('qwerty')
        self.spins.set(42)
        self.check.set(1)
        self.option.set(1)
        self.ListBox.delete(0, tk.END)
        
        for i in self.values:
            s = "{0}".format(i,)
            self.ListBox.insert(tk.END, s)
     
        self.ListBox.selection_set(1)

    def on_listbox_select(self, evt=None):

        if self.ListBox.curselection():

            index = self.ListBox.curselection()
            s = self.ListBox.get(index[0])
            print("on_listbox_select: index = {0} values = {1}".format(index, s))

    def on_listbox_double_button(self, evt=None):

        if self.ListBox.curselection():
            index = self.ListBox.curselection()
            s = self.ListBox.get(index[0])
            print("on_listbox_double_button: index = {0} values = {1}".format(index, s))

    def periodic_call(self):
        """This funciont check the data returned from the clock class queue."""

        self.parent.clock.check_queue(self.status_bar_text)
        
        if self.parent.clock.is_alive():
            self.after(1, self.periodic_call)
        else:
            pass                    
        
    def on_close(self, evt=None):
        self.parent.on_exit()


class App(tk.Tk):
    """Main Application start here"""
    def __init__(self, *args, **kwargs):
        super().__init__()

        self.args = args
        self.kwargs = kwargs
        self.protocol("WM_DELETE_WINDOW", self.on_exit)
        self.set_style(kwargs["style"])  
        self.set_title(kwargs["title"])
        self.resizable(width=False, height=False)

        #start clock on a separate thread...
        self.set_clock()
        
        w = Main(self)
        w.on_open()
        w.pack(fill=tk.BOTH, expand=1)

    def set_clock(self,):
        self.clock = self.get_clock()
        self.clock.start()
        
    def get_clock(self,):
        """Instance the clock."""
        return Clock()
    
    def set_style(self, which):
        self.style = ttk.Style()
        self.style.theme_use(which)
        
    def set_title(self, title):
        s = "{0}".format(title)
        self.title(s)
        
    def on_exit(self):
        """Close all"""
        msg = "Do you want to quit?"
        if messagebox.askokcancel(self.title(), msg, parent=self):
            #stop the thread
            if self.clock is not None:
                self.clock.stop()
            self.destroy()

def main():

    args = []

    for i in sys.argv:
        args.append(i)

    #('winnative', 'clam', 'alt', 'default', 'classic', 'vista', 'xpnative')
    kwargs = {"style":"clam", "title":"Simple App",}

    app = App(*args, **kwargs)

    app.mainloop()

if __name__ == '__main__':
    main()            
    

主应用

在此处输入图像描述

With respect to关于https://stackoverflow.com/a/47920128/15959848 I solved my problem. https://stackoverflow.com/a/47920128/15959848我解决了我的问题。 I have created an additional thread so that the GUI and the function each have a thread in which they can be executed.我创建了一个额外的线程,以便 GUI 和函数每个都有一个可以执行它们的线程。

class pseudo_example():


    def app(self,async_loop):
        self.root = tk.Tk()
        self.root.minsize(100,100)

        self.start_button = tk.Button(self.root, text="start", command=lambda: self.create_await_fun(async_loop))
        self.start_button.pack()

        self.testfield = tk.Label(self.root, text="output")
        self.testfield.pack()
        self.root.mainloop()

    def create_await_fun(self,async_loop):
        threading.Thread(target=self.asyncio_thread, args=(async_loop,)).start()
        self.start_button["relief"] = "sunken"
        self.start_button["state"] = "disabled"

    def asyncio_thread(self, async_loop):
        async_loop.run_until_complete(self.await_fun())

    async def await_fun(self):

        self.testfield["text"] = "start waiting"
        self.root.update_idletasks()

        await asyncio.sleep(2)

        self.testfield["text"] = "end waiting"
        self.root.update_idletasks()

        await asyncio.sleep(1)

        self.testfield["text"] = "output"
        self.root.update_idletasks()
        self.start_button["relief"] = "raised"
        self.start_button["state"] = "normal"


if __name__ == '__main__':
    gui = pseudo_example()
    async_loop = asyncio.get_event_loop()
    gui.app(async_loop)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM