使用来自第二个线程的数据更新 Tkinter-GUI 中的数据
Update data in a Tkinter-GUI with data from a second Thread
问题是我的解决方案是否是一种使用来自另一个线程的数据更新 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 <
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()
真正的应用
这个例子被简化了。真正的应用程序是从许多不同的网站下载(feedparser
)数百个 xml 文件(新闻源)。
结果显示在 Tkinter.Treeview
中,其中每个 xml 文件在 TreeView
中都有一个条目。 e. G。 xml 文件中的条目数显示在 TreeView
的条目中(例如 "Time Magazine (12 entries)")。
每次 xml 文件下载完成并且 not 完成 all xml 文件下载后,都应执行此操作完成了。
此解决方案基于其他人的评论。它使用 queue.Queue
在两个线程之间共享数据。 Tkinter GUI/Thread 使用 1 秒计时器检查队列中是否有新数据并使用它来刷新其标签。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# based on <
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()
此示例基于 。
不确定这是否是一个优雅的解决方案。请随时对此进行编辑。我的目标是让其他人可以重复使用我的问题和答案。
我用另一种方式解决了。
我不是专家,但我发现这个解决方案对我有用。欢迎任何改进!
- 在
main()
. 中创建 Tk
对象(通常称为 root
)
- 在
main()
中创建一个线程 运行 您的应用程序(我假设它是 Class
)并将 root
作为参数传递给它。
- 在
main()
中执行root.mainloop()
- 在应用程序
__init__
中定义 GUI 按钮、标签、条目... class
- 像往常一样使用 Tkinter 方法更新应用程序中的 GUI 按钮、标签、条目...class(
get()
、set()
、...)
这是一个获得灵感的模板:
from tkinter import *
import _thread
class App(threading.Thread):
def __init__(self, root, my_param2, my_param3):
# Creare GUI
self.root = root
# Your code here
def method1(self):
# Your code here
def main():
# Create GUI
root = Tk(className='MyApp')
# Create 2 threads
num_threads = 2
for t in range(num_threads):
try:
_thread.start_new_thread(App, (root, my_param2, my_param3, ) )
except:
print('Error: can not create a thread')
# tkinter main loop
root.mainloop()
print ('You don\'t see this message')
if __name__ == "__main__":
main()
问题是我的解决方案是否是一种使用来自另一个线程的数据更新 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 <
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()
真正的应用
这个例子被简化了。真正的应用程序是从许多不同的网站下载(feedparser
)数百个 xml 文件(新闻源)。
结果显示在 Tkinter.Treeview
中,其中每个 xml 文件在 TreeView
中都有一个条目。 e. G。 xml 文件中的条目数显示在 TreeView
的条目中(例如 "Time Magazine (12 entries)")。
每次 xml 文件下载完成并且 not 完成 all xml 文件下载后,都应执行此操作完成了。
此解决方案基于其他人的评论。它使用 queue.Queue
在两个线程之间共享数据。 Tkinter GUI/Thread 使用 1 秒计时器检查队列中是否有新数据并使用它来刷新其标签。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# based on <
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()
此示例基于
我用另一种方式解决了。 我不是专家,但我发现这个解决方案对我有用。欢迎任何改进!
- 在
main()
. 中创建 - 在
main()
中创建一个线程 运行 您的应用程序(我假设它是Class
)并将root
作为参数传递给它。 - 在
main()
中执行 - 在应用程序
__init__
中定义 GUI 按钮、标签、条目... class - 像往常一样使用 Tkinter 方法更新应用程序中的 GUI 按钮、标签、条目...class(
get()
、set()
、...)
Tk
对象(通常称为 root
)
root.mainloop()
这是一个获得灵感的模板:
from tkinter import *
import _thread
class App(threading.Thread):
def __init__(self, root, my_param2, my_param3):
# Creare GUI
self.root = root
# Your code here
def method1(self):
# Your code here
def main():
# Create GUI
root = Tk(className='MyApp')
# Create 2 threads
num_threads = 2
for t in range(num_threads):
try:
_thread.start_new_thread(App, (root, my_param2, my_param3, ) )
except:
print('Error: can not create a thread')
# tkinter main loop
root.mainloop()
print ('You don\'t see this message')
if __name__ == "__main__":
main()