buh*_*htz 1 python asynchronous tkinter python-multithreading python-asyncio
问题是我的解决方案是否是一种使用来自另一个线程的数据更新 Tkinter-GUI 的保存和 pythonic 方式?是Lock必需的吗?或者怎么能在Queue这里提供帮助?此示例运行良好,但原始应用程序需要处理复杂得多的数据。
请专注于AsyncioThread.create_dummy_data()最小的工作示例。该示例有两个线程。一个运行Tkinter -mainloop,第二个线程运行asyncio -loop。异步循环模拟获取一些数据并tkinter.Label用这些数据刷新一些数据。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# restrict to Python3.5 or higher because of asyncio syntax
# based on </sf/answers/3354408991/>
from tkinter import *
import asyncio
import threading
import random
class AsyncioThread(threading.Thread):
def __init__(self, asyncio_loop, theWindow):
self.asyncio_loop = asyncio_loop
self.theWindow = theWindow
self.maxData = len(theWindow.varData)
threading.Thread.__init__(self)
def run(self):
self.asyncio_loop.run_until_complete(self.do_data())
async def do_data(self):
""" Creating and starting 'maxData' asyncio-tasks. """
tasks = [
self.create_dummy_data(number)
for number in range(self.maxData)
]
completed, pending = await asyncio.wait(tasks)
results = [task.result() for task in completed]
print('\n'.join(results))
async def create_dummy_data(self, number):
""" One task. """
sec = random.randint(1, 3)
data = '{}:{}'.format(number, random.random())
await asyncio.sleep(sec)
# IS THIS SAVE?
self.theWindow.varData[number].set(data)
print('Thread-ID: {}\tsec: {}\n\t{}' \
.format(threading.get_ident(), sec, data))
return data
class TheWindow:
def __init__(self, maxData):
# asyncio loop will run in an extra Thread
self.asyncio_loop = asyncio.get_event_loop()
# the GUI main object
self.root = Tk()
# create the data variable
self.varData = []
for i in range(maxData):
self.varData.append(StringVar())
self.varData[i].set('<default>')
# Button to start the asyncio tasks
Button(master=self.root,
text='Start Asyncio Tasks',
command=lambda:self.do_asyncio()).pack()
# Frames to display data from the asyncio tasks
for i in range(maxData):
Label(master=self.root, textvariable=self.varData[i]).pack()
# Button to check if the GUI is freezed
Button(master=self.root,
text='Freezed???',
command=self.do_freezed).pack()
def do_freezed(self):
""" Button-Event-Handler to see if a button on GUI works.
The GOAL of this example is to make this button clickable
while the other thread/asyncio-tasks are working. """
print('Tkinter is reacting. Thread-ID: {}'
.format(threading.get_ident()))
def do_asyncio(self):
""" Button-Event-Handler starting the asyncio part in a separate thread. """
thread = AsyncioThread(self.asyncio_loop, self)
thread.start()
if __name__ == '__main__':
window = TheWindow(5)
window.root.mainloop()
Run Code Online (Sandbox Code Playgroud)
此示例已简化。真正的应用程序是feedparser从同样多的不同网站下载(使用)数百个 xml 文件(新闻源)。结果显示在Tkinter.Treeview.xml 文件中,其中每个 xml 文件都有一个条目TreeView。例如,xml 文件中的条目数显示在TreeView(例如“时代杂志(12 个条目)”)的条目中。这应该在每次xml 文件下载完成后执行,而不是在所有xml 文件下载完成后执行。
此解决方案基于其他人的评论。它用于queue.Queue在两个线程之间共享数据。Tkinter GUI/Thread 使用 1 秒计时器来检查新数据是否在队列中并使用它来刷新其标签。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# based on </sf/answers/3354408991/>
from tkinter import *
import asyncio
import threading
import random
import queue
class AsyncioThread(threading.Thread):
def __init__(self, the_queue, max_data):
self.asyncio_loop = asyncio.get_event_loop()
self.the_queue = the_queue
self.max_data = max_data
threading.Thread.__init__(self)
def run(self):
self.asyncio_loop.run_until_complete(self.do_data())
async def do_data(self):
""" Creating and starting 'maxData' asyncio-tasks. """
tasks = [
self.create_dummy_data(key)
for key in range(self.max_data)
]
await asyncio.wait(tasks)
async def create_dummy_data(self, key):
""" Create data and store it in the queue. """
sec = random.randint(1, 10)
data = '{}:{}'.format(key, random.random())
await asyncio.sleep(sec)
self.the_queue.put((key, data))
class TheWindow:
def __init__(self, max_data):
# thread-safe data storage
self.the_queue = queue.Queue()
# the GUI main object
self.root = Tk()
# create the data variable
self.data = []
for key in range(max_data):
self.data.append(StringVar())
self.data[key].set('<default>')
# Button to start the asyncio tasks
Button(master=self.root,
text='Start Asyncio Tasks',
command=lambda: self.do_asyncio()).pack()
# Frames to display data from the asyncio tasks
for key in range(max_data):
Label(master=self.root, textvariable=self.data[key]).pack()
# Button to check if the GUI is freezed
Button(master=self.root,
text='Freezed???',
command=self.do_freezed).pack()
def refresh_data(self):
"""
"""
# do nothing if the aysyncio thread is dead
# and no more data in the queue
if not self.thread.is_alive() and self.the_queue.empty():
return
# refresh the GUI with new data from the queue
while not self.the_queue.empty():
key, data = self.the_queue.get()
self.data[key].set(data)
print('RefreshData...')
# timer to refresh the gui with data from the asyncio thread
self.root.after(1000, self.refresh_data) # called only once!
def do_freezed(self):
""" Button-Event-Handler to see if a button on GUI works.
The GOAL of this example is to make this button clickable
while the other thread/asyncio-tasks are working. """
print('Tkinter is reacting. Thread-ID: {}'
.format(threading.get_ident()))
def do_asyncio(self):
"""
Button-Event-Handler starting the asyncio part in a separate
thread.
"""
# create Thread object
self.thread = AsyncioThread(self.the_queue, len(self.data))
# timer to refresh the gui with data from the asyncio thread
self.root.after(1000, self.refresh_data) # called only once!
# start the thread
self.thread.start()
if __name__ == '__main__':
window = TheWindow(10)
window.root.mainloop()
Run Code Online (Sandbox Code Playgroud)
此示例基于/sf/answers/3354408991/。不确定这是否是一个优雅的解决方案。请随意编辑此内容。我的目标是让我的问题和答案可以被其他人重复使用。