gpt4 book ai didi

python - tkinter 和 asyncio,窗口拖动/调整大小阻止事件循环,单线程

转载 作者:行者123 更新时间:2023-12-01 01:04:44 27 4
gpt4 key购买 nike

Tkinter 和 asyncio 一起工作时存在一些问题:它们都是想要无限期阻塞的事件循环,如果您尝试在同一线程上运行它们,其中一个将阻止另一个执行。这意味着,如果您想运行 tk 事件循环 (Tk.mainloop()),则所有 asyncio 任务都不会运行;如果你想运行 asyncio 事件循环,你的 GUI 将永远不会绘制到屏幕上。为了解决这个问题,我们可以通过调用 Tk.update() 作为 asyncio 任务来模拟 Tk 的事件循环(如下面的 ui_update_task() 所示)。除了一个问题之外,这对我来说效果很好:窗口管理器事件阻止异步事件循环。其中包括窗口拖动/调整大小操作。我不需要调整大小,因此我在程序中禁用了它(在下面的 MCVE 中未禁用),但用户可能需要拖动窗口,我非常希望我的应用程序在此期间继续运行.

这个问题的目的是看看是否可以在单个线程中解决这个问题。这里和其他地方有几个答案,通过在一个线程中运行 tk 的事件循环和在另一个线程中运行 asyncio 的事件循环来解决此问题,通常使用队列将数据从一个线程传递到另一个线程。我对此进行了测试,并确定由于多种原因,这对于我的问题来说是一个不受欢迎的解决方案。如果可能的话,我希望在单个线程中完成此任务。

我也尝试过overrideredirect(True)完全删除标题栏并将其替换为仅包含标签和 X 按钮的 tk.Frame,并实现了我自己的拖动方法。这也有删除任务栏图标的不良副作用,可以补救by making an invisible root window that pretends to be your real window 。这种解决方法的兔子洞可能会更糟,但我真的不想重新实现和破解这么多基本的窗口操作。但是,如果我找不到这个问题的解决方案,这很可能就是我采取的路线。

import asyncio
import tkinter as tk


class tk_async_window(tk.Tk):
def __init__(self, loop, update_interval=1/20):
super(tk_async_window, self).__init__()
self.protocol('WM_DELETE_WINDOW', self.close)
self.geometry('400x100')
self.loop = loop
self.tasks = []
self.update_interval = update_interval

self.status = 'working'
self.status_label = tk.Label(self, text=self.status)
self.status_label.pack(padx=10, pady=10)

self.close_event = asyncio.Event()

def close(self):
self.close_event.set()

async def ui_update_task(self, interval):
while True:
self.update()
await asyncio.sleep(interval)

async def status_label_task(self):
"""
This keeps the Status label updated with an alternating number of dots so that you know the UI isn't
frozen even when it's not doing anything.
"""
dots = ''
while True:
self.status_label['text'] = 'Status: %s%s' % (self.status, dots)
await asyncio.sleep(0.5)
dots += '.'
if len(dots) >= 4:
dots = ''

def initialize(self):
coros = (
self.ui_update_task(self.update_interval),
self.status_label_task(),
# additional network-bound tasks
)
for coro in coros:
self.tasks.append(self.loop.create_task(coro))

async def main():
gui = tk_async_window(asyncio.get_event_loop())
gui.initialize()
await gui.close_event.wait()
gui.destroy()

if __name__ == '__main__':
asyncio.run(main(), debug=True)

如果运行上面的示例代码,您将看到一个带有标签的窗口,其中显示: Status: working接下来是 0-3 个点。如果按住标题栏,您会注意到这些点将停止动画,这意味着异步事件循环被阻止。这是因为调用self.update()被阻止在 ui_update_task() 。释放标题栏后,您应该在控制台中收到来自 asyncio 的消息: Executing <Handle <TaskWakeupMethWrapper object at 0x041F4B70>(<Future finis...events.py:396>) created at C:\Program Files (x86)\Python37-32\lib\asyncio\futures.py:288> took 1.984 seconds秒数是您拖动窗口的时间。我想要的是某种方法来处理拖动事件而不阻塞 asyncio 或生成新线程。有什么办法可以实现这一点吗?

最佳答案

实际上,您正在 asyncio 事件循环内执行单独的 Tk 更新,并且遇到 update() 阻塞的地方。另一种选择是反转逻辑并从 Tkinter 计时器内部调用异步事件循环的单个步骤 - 即使用 Widget.after继续调用run_once .

以下是经过上述更改的代码:

import asyncio
import tkinter as tk


class tk_async_window(tk.Tk):
def __init__(self, loop, update_interval=1/20):
super(tk_async_window, self).__init__()
self.protocol('WM_DELETE_WINDOW', self.close)
self.geometry('400x100')
self.loop = loop
self.tasks = []

self.status = 'working'
self.status_label = tk.Label(self, text=self.status)
self.status_label.pack(padx=10, pady=10)

self.after(0, self.__update_asyncio, update_interval)
self.close_event = asyncio.Event()

def close(self):
self.close_event.set()

def __update_asyncio(self, interval):
self.loop.call_soon(self.loop.stop)
self.loop.run_forever()
if self.close_event.is_set():
self.quit()
self.after(int(interval * 1000), self.__update_asyncio, interval)

async def status_label_task(self):
"""
This keeps the Status label updated with an alternating number of dots so that you know the UI isn't
frozen even when it's not doing anything.
"""
dots = ''
while True:
self.status_label['text'] = 'Status: %s%s' % (self.status, dots)
await asyncio.sleep(0.5)
dots += '.'
if len(dots) >= 4:
dots = ''

def initialize(self):
coros = (
self.status_label_task(),
# additional network-bound tasks
)
for coro in coros:
self.tasks.append(self.loop.create_task(coro))

if __name__ == '__main__':
gui = tk_async_window(asyncio.get_event_loop())
gui.initialize()
gui.mainloop()
gui.destroy()

不幸的是,我无法在我的机器上测试它,因为阻塞 update() 的问题似乎不会出现在 Linux 上,在 Linux 上,窗口的移动是由窗口管理器组件处理的桌面而不是程序本身。

关于python - tkinter 和 asyncio,窗口拖动/调整大小阻止事件循环,单线程,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55464512/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com