如何让GTK界面与asyncio协同工作,避免GUI冻结?
解决GTK3与asyncio协同运行避免GUI冻结的方案
你的代码核心问题是每次点击按钮调用asyncio.run()会启动独立的asyncio事件循环,阻塞了GTK的主循环,导致GUI冻结。要实现GTK和asyncio的无缝兼容,需要让两个事件循环协同运行,而不是各自独立执行。
最优实现思路
GTK基于GLib主循环,我们可以将asyncio的事件循环整合到GLib主循环中,让两者共享同一个线程的事件调度,这样异步任务执行时,GTK仍能响应UI操作,不会出现冻结。
修正后的完整代码
import asyncio import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk, GLib async def simulate_get_results(): await asyncio.sleep(5) return ['Imagine these are results from an API'] class Window(Gtk.Window): def __init__(self): super().__init__() self.search_button = Gtk.Button(label='Press me') # 绑定按钮点击回调 self.search_button.connect('clicked', self.on_button_clicked) self.add(self.search_button) def on_button_clicked(self, button: Gtk.Button): # 将异步任务加入asyncio事件循环,不阻塞GTK主循环 asyncio.create_task(self.update_button_label(button)) async def update_button_label(self, button: Gtk.Button): results = await simulate_get_results() # 直接更新UI,事件循环整合后天然在主线程,无需额外同步 button.set_label(results[0]) async def main(): win = Window() win.connect('destroy', Gtk.main_quit) win.show_all() # 获取asyncio事件循环 loop = asyncio.get_event_loop() # 将asyncio事件循环整合进GLib主循环,实现协同调度 def run_asyncio_iteration(source, condition): loop.call_soon(loop.stop) loop.run_forever() return True GLib.io_add_watch(loop._selector._fd, GLib.PRIORITY_DEFAULT, run_asyncio_iteration) # 启动GTK主循环 Gtk.main() if __name__ == '__main__': asyncio.run(main())
关键细节说明
- 事件循环整合:通过
GLib.io_add_watch监听asyncio事件循环的文件描述符,每当asyncio有事件需要处理时,GLib主循环会调度执行asyncio的事件迭代,确保两个循环都能正常响应事件。 - 异步任务调度:按钮回调中用
asyncio.create_task()将异步任务加入事件循环,不会阻塞当前GTK主循环的执行。 - UI线程安全:所有操作都在同一个主线程的事件循环中,GTK的UI更新操作天然线程安全,完美支持高频通信场景(比如搜索框实时输入)。
内容的提问来源于stack exchange,提问作者Grass Lunatic
相关产品推荐
相关产品推荐

