我想使用asyncio
与tkinter
GUI相结合。我是asyncio
的新手,对它的理解不是很详细。这里的示例在单击第一个按钮时启动10个任务。这个任务只是在几秒钟内用睡眠()
来模拟工作。
示例代码在Python3.6.4rc1
下运行良好。但问题是GUI被冻结了。当我按下第一个按钮并启动10个异步任务时,我不能在GUI中按下第二个按钮,直到所有任务都完成。图形用户界面永远不应该冻结——这是我的目标。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from tkinter import *
from tkinter import messagebox
import asyncio
import random
def do_freezed():
""" Button-Event-Handler to see if a button on GUI works. """
messagebox.showinfo(message='Tkinter is reacting.')
def do_tasks():
""" Button-Event-Handler starting the asyncio part. """
loop = asyncio.get_event_loop()
try:
loop.run_until_complete(do_urls())
finally:
loop.close()
async def one_url(url):
""" One task. """
sec = random.randint(1, 15)
await asyncio.sleep(sec)
return 'url: {}\tsec: {}'.format(url, sec)
async def do_urls():
""" Creating and starting 10 tasks. """
tasks = [
one_url(url)
for url in range(10)
]
completed, pending = await asyncio.wait(tasks)
results = [task.result() for task in completed]
print('\n'.join(results))
if __name__ == '__main__':
root = Tk()
buttonT = Button(master=root, text='Asyncio Tasks', command=do_tasks)
buttonT.pack()
buttonX = Button(master=root, text='Freezed???', command=do_freezed)
buttonX.pack()
root.mainloop()
...由于此错误,我无法再次运行该任务。
Exception in Tkinter callback
Traceback (most recent call last):
File "/usr/lib/python3.6/tkinter/__init__.py", line 1699, in __call__
return self.func(*args)
File "./tk_simple.py", line 17, in do_tasks
loop.run_until_complete(do_urls())
File "/usr/lib/python3.6/asyncio/base_events.py", line 443, in run_until_complete
self._check_closed()
File "/usr/lib/python3.6/asyncio/base_events.py", line 357, in _check_closed
raise RuntimeError('Event loop is closed')
RuntimeError: Event loop is closed
为什么多线程是一个可能的解决方案?只有两个线程-每个循环都有自己的线程?
编辑:在回顾这个问题和答案之后,它几乎与所有GUI库(例如PygObject/Gtk、wxWidgets、Qt等)都相关。
我参加聚会有点晚了,但如果你不是针对Windows,你可以使用aiotkinter来实现你想要的。我修改了您的代码以向您展示如何使用此软件包:
from tkinter import *
from tkinter import messagebox
import asyncio
import random
import aiotkinter
def do_freezed():
""" Button-Event-Handler to see if a button on GUI works. """
messagebox.showinfo(message='Tkinter is reacting.')
def do_tasks():
task = asyncio.ensure_future(do_urls())
task.add_done_callback(tasks_done)
def tasks_done(task):
messagebox.showinfo(message='Tasks done.')
async def one_url(url):
""" One task. """
sec = random.randint(1, 15)
await asyncio.sleep(sec)
return 'url: {}\tsec: {}'.format(url, sec)
async def do_urls():
""" Creating and starting 10 tasks. """
tasks = [
one_url(url)
for url in range(10)
]
completed, pending = await asyncio.wait(tasks)
results = [task.result() for task in completed]
print('\n'.join(results))
if __name__ == '__main__':
asyncio.set_event_loop_policy(aiotkinter.TkinterEventLoopPolicy())
loop = asyncio.get_event_loop()
root = Tk()
buttonT = Button(master=root, text='Asyncio Tasks', command=do_tasks)
buttonT.pack()
buttonX = Button(master=root, text='Freezed???', command=do_freezed)
buttonX.pack()
loop.run_forever()
在对代码稍作修改时,我在主线程中创建了asyncioevent_loop
,并将其作为参数传递给asyncio线程。现在,获取网址时,Tkinter不会冻结。
from tkinter import *
from tkinter import messagebox
import asyncio
import threading
import random
def _asyncio_thread(async_loop):
async_loop.run_until_complete(do_urls())
def do_tasks(async_loop):
""" Button-Event-Handler starting the asyncio part. """
threading.Thread(target=_asyncio_thread, args=(async_loop,)).start()
async def one_url(url):
""" One task. """
sec = random.randint(1, 8)
await asyncio.sleep(sec)
return 'url: {}\tsec: {}'.format(url, sec)
async def do_urls():
""" Creating and starting 10 tasks. """
tasks = [one_url(url) for url in range(10)]
completed, pending = await asyncio.wait(tasks)
results = [task.result() for task in completed]
print('\n'.join(results))
def do_freezed():
messagebox.showinfo(message='Tkinter is reacting.')
def main(async_loop):
root = Tk()
Button(master=root, text='Asyncio Tasks', command= lambda:do_tasks(async_loop)).pack()
Button(master=root, text='Freezed???', command=do_freezed).pack()
root.mainloop()
if __name__ == '__main__':
async_loop = asyncio.get_event_loop()
main(async_loop)
试图同时运行两个事件循环是一个可疑的命题。然而,由于root.mainloop只是重复调用root.update,因此可以通过将更新作为异步任务重复调用来模拟主循环。这里有一个这样做的测试程序。我认为向tkinter任务添加asyncio任务会起作用。我检查了它仍然运行3.7.0a2。
"""Proof of concept: integrate tkinter, asyncio and async iterator.
Terry Jan Reedy, 2016 July 25
"""
import asyncio
from random import randrange as rr
import tkinter as tk
class App(tk.Tk):
def __init__(self, loop, interval=1/120):
super().__init__()
self.loop = loop
self.protocol("WM_DELETE_WINDOW", self.close)
self.tasks = []
self.tasks.append(loop.create_task(self.rotator(1/60, 2)))
self.tasks.append(loop.create_task(self.updater(interval)))
async def rotator(self, interval, d_per_tick):
canvas = tk.Canvas(self, height=600, width=600)
canvas.pack()
deg = 0
color = 'black'
arc = canvas.create_arc(100, 100, 500, 500, style=tk.CHORD,
start=0, extent=deg, fill=color)
while await asyncio.sleep(interval, True):
deg, color = deg_color(deg, d_per_tick, color)
canvas.itemconfigure(arc, extent=deg, fill=color)
async def updater(self, interval):
while True:
self.update()
await asyncio.sleep(interval)
def close(self):
for task in self.tasks:
task.cancel()
self.loop.stop()
self.destroy()
def deg_color(deg, d_per_tick, color):
deg += d_per_tick
if 360 <= deg:
deg %= 360
color = '#%02x%02x%02x' % (rr(0, 256), rr(0, 256), rr(0, 256))
return deg, color
loop = asyncio.get_event_loop()
app = App(loop)
loop.run_forever()
loop.close()
tk更新开销和时间分辨率都随着时间间隔的减小而增加。对于gui更新,与动画相反,每秒20次就足够了。
我最近成功地运行了异步def协程,包含了tkinter调用,并等待主循环。原型使用异步任务和期货,但我不知道添加普通异步任务是否有效。如果一个人想一起运行asyncio和tkinter任务,我认为用asyncio循环运行tk更新是一个更好的主意。
编辑:至少如上所述,没有异步def协程的异常会杀死协程,但会在某个地方被捕获并丢弃。无声的错误非常令人讨厌。
你能解释一下,当作为一个单独的进程执行辅助函数时,我是如何防止python GUI冻结的吗? 我编写了一个python GUI,点击一个按钮,就可以通过多处理模块启动一个进程。我决定使用多处理而不是线程,因为我喜欢选择启动、暂停、恢复和终止进程。 不幸的是,当辅助进程运行时,GUI会冻结并失去响应,因此我无法按下“暂停”按钮。 图形用户界面的冻结问题在stackoverflow上报告过几次,但是这
我有一个Python 3。x报表创建者,其I/O绑定(由于SQL而非python),在创建报表时主窗口将“锁定”数分钟。 所需要的只是在锁定GUI时使用标准窗口操作(移动、调整大小/最小化、关闭等)(GUI上的所有其他内容都可以保持“冻结”,直到所有报告完成)。 添加20181129:换句话说,tkinter必须只控制应用程序窗口的内容,并将所有标准(外部)窗口控件的处理留给O/S。如果我能做到这
问题内容: 我知道此问题是由主线程上的sleep或wait调用引起的,有关如何解决此问题的答案是将方法放入单独的线程中,然后使该线程进入休眠状态。但是代码是一团糟,真的没有时间将其整理出来并分成单独的线程,并且想知道是否还有其他方法可以做到这一点?即使这不是使用GUI的最干净或最常见的做法。我只需要从方法中暂停一秒钟。 问题答案: 不创建单独的线程就无法做到这一点。用Java创建线程很容易。唯一要
按下按钮后,我的界面冻结。我使用线程,但我不知道为什么仍然挂起。任何帮助都将不胜感激。提前谢谢
问题内容: 我有一个程序在其中加载文件,同时显示一个窗口以通知用户正在加载文件。我决定制作一个FileLoader类,它是一个实际上处理了文件加载的SwingWorker类,以及一个实现PropertyChangeListener的ProgressWindow,用于通知用户传递给它的SwingWorker的状态。 我的代码当前如下所示: 问题是,每当我调用loader.get()时,它都会冻结GU
我是python新手,制作了一个Tkinter应用程序,当按下开始按钮时,它将执行目录中存在的所有python文件。我的GUI也有progressbar来查看当前的进度。 这是我的代码 但我的问题是,每当我按下开始按钮,GUI就会冻结,代码开始编译,完成后,GUI就会解冻,Progressbar会立即填满自己。。。 因此,我希望GUI在处理时不要冻结,并在Progressbar上显示正确的进度。