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

2023-11-29

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

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

我也尝试过overrideredirect(True)完全删除标题栏并将其替换为仅包含标签和 X 按钮的 tk.Frame,并实现了我自己的拖动方法。这也有删除任务栏图标的不良副作用,这是可以补救的通过制作一个隐形的根窗口来假装是您真正的窗口。这种解决方法的兔子洞可能会更糟,但我真的不想重新实现和破解这么多基本的窗口操作。但是,如果我找不到解决这个问题的方法,这很可能就是我采取的路线。

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 计时器内部调用 asyncio 事件循环的单个步骤 - 即使用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 上,其中窗口的移动是由桌面的窗口管理器组件而不是程序本身来处理的。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

tkinter 和 asyncio,窗口拖动/调整大小阻止事件循环,单线程 的相关文章

随机推荐

  • 如何修改SVG图像作为背景图像的填充颜色?

    将 SVG 输出直接与页面代码内联放置 我可以简单地使用 CSS 修改填充颜色 如下所示 polygon mystar fill blue circle mycircle fill green 这很好用 但是我正在寻找一种方法来修改 SVG
  • 两个不同的 jenkins 构建器可以存在于同一个 hpi 中并共享相同的全局配置吗?

    我需要创建两个不同的 Jenkins Builder 类 每个都执行不同的操作 并且每个都需要自己的 jelly 但是 两者都需要相同的全局配置 global jelly 该配置指定主机和一些用户凭据 两种构建器类型的实例在执行执行期间将使
  • 向量值的不同组合

    假设我有一个由 n 个值组成的向量 我想获得其值的不同组合 例如 如果我有 vect a b c 我想要的不同组合是 a b c a b a c b c a b c 请注意 例如 a b 与 b a 相同 因此我不需要同时保留它们 计数自0
  • 如何在 Eclipse 中的可执行 .jar 文件中包含资源文件夹?

    我需要创建一个应用程序 使用各种参数 例如高度 体积或底面积 对各种类型的多边形进行排序 具有多边形参数 排序类型 排序方法的文件名参数将通过命令行传递 该文件位于项目中 src 文件夹外部的资源文件夹中 我已经实现了所有程序 当我通过 e
  • 无法动态创建和附加 div 和 span

    我正在编写一个 HTML 代码 其中有一个 div 说y这是在我的 HTML 正文中 有一个按钮 当用户单击此按钮时 我想要执行以下操作 创建另一个 div 类为smallBar 里面这个div 我想创建3个跨度 添加此 总计smallBa
  • 使用所有时区和有/无 DST 的日期进行单元测试

    如何使此单元测试在所有时区中通过 无论 DST 是否处于活动状态 import static org junit Assert import java text SimpleDateFormat import java util Date
  • 包含新的测试目录 Maven Surefire 插件

    现有结构 src test java gt 所有 java 单元测试 Maven Surefire 插件可以轻松获取此信息 现在 除了这些java单元测试用例之外 我还想包括一些groovy测试用例 并且我想将它们放在src test gr
  • Android - 从光标获取专辑艺术家

    我目前正在创建一个音乐播放器 并且正在使用光标检索设备上的音乐 mCursor getContentResolver query MediaStore Audio Media EXTERNAL CONTENT URI requestedCo
  • python-docx - 显示为普通段落的列表

    我正在尝试将数字和项目符号列表插入到现有的 Word 文档中 但是它们显示为普通段落 Open up existing document document Document existing document docx Add style
  • selenium 无法对网页元素进行屏幕截图

    我可以使用 Firefox get screenshot as file 2 png 对整个页面进行屏幕截图 但是当我使用passage screenshot 1 png 对网页元素进行屏幕截图时 它总是会引发此异常 selenium co
  • 嵌入字体和 11 月字体有什么区别?

    在书中我看到了例子 BaseFont bf BaseFont createFont KozMinPro Regular Identity V BaseFont NOT EMBEDDED Font font new Font bf 20 Ve
  • 将 Blazor .NET 6 WASM 部署到 GitHub 页面

    我正在尝试让 Blazor WASM 在 GitHub 页面中工作 我关注了这个视频 https www youtube com watch v nNxII6jvPvQ 我将它部署到这里 扩展 GH 页面 来源在这里 GH 页面源 我收到此
  • 如何用管道描述推荐基线

    我试图找到复合基线中关联的所有组件基线 我可以使用以下方式实现它 cleartool desc fmt rec bls CXp stream My Integration My PVOB I would save the receommen
  • 异步始终等待激活

    我想弄清楚是什么async await关键字是全部 但输出并不是我所期望的 控制台应用程序如下 class Program static void Main string args Console WriteLine Foo called
  • $and 查询没有返回结果

    好吧 这个简直要了我的命 也许已经晚了 我忘记了一些事情 但这应该有效 出于测试目的 我收集了大约 6000 个文档 有一个属性叫Priority在每个实例中其值为 2 以下两个查询分别返回all6000 个文档 Priority gt 1
  • MKMapKit 可拖动注释和绘制多边形

    我目前正在尝试允许用户向地图添加图钉 然后绘制连接这些图钉的多边形 但是我想扩展它以允许用户能够拖动引脚并且多边形将相应地更新 MKMapView 根据坐标数组中的排列从坐标数组中绘制多边形 如果我没有记错的话 我现在面临的问题是在用户重新
  • 无法启动 Rails Server - 找不到 JavaScript 运行时

    pallav pallav System Product Name Workspace blog rails server home pallav rvm gems ruby 2 0 0 p353 gems execjs 2 0 2 lib
  • pandas:相当于 SQL 的 datediff() 的函数?

    Python 的 pandas 中是否有与 SQL 的 datediff 函数等效的函数 这个问题的答案 在 DataFrame pandas 中添加包含日期之间天数的列解释如何计算天数差异 例如 gt gt gt pd to dateti
  • 如果加载时间太长,如何让 selenium 重新加载所需的 url

    如果加载过程花费太长时间 我希望 selenium 强制浏览器重新加载正在加载的页面 从 StackOverflow 我知道这段代码 new WebDriverWait driver 30 until ExpectedCondition
  • tkinter 和 asyncio,窗口拖动/调整大小阻止事件循环,单线程

    Tkinter 和 asyncio 一起工作时存在一些问题 它们都是想要无限期阻塞的事件循环 如果您尝试在同一线程上运行它们 其中一个将阻止另一个执行 这意味着 如果您想运行 tk 事件循环 Tk mainloop 则所有 asyncio