需求是用 tkinter 制作的 gui 工具,点击 [开始] 后在异步函数里 while 循环,点击 [停止] 后让 while 停止
目前的问题是 asyncio.create_task
遇到 asyncio.sleep
就中断了
实际的项目中用 threading.Thread()解决了,但是不甘心还是想试试用异步解决,但还没找到解决的方法
就目前的体验来说,python 异步用起来的体验没有 node.js 来的舒服,限制挺多的
import asyncio
import time
import tkinter
from tkinter import ttk
class Window:
def __init__(self):
self.__do_while = False
root = tkinter.Tk()
root.minsize(200, 200)
frame = ttk.Frame()
frame.pack(fill=tkinter.BOTH)
ttk.Button(frame, text='开始', command=self.start).pack()
ttk.Button(frame, text='停止', command=self.stop).pack(pady=10)
root.mainloop()
def start(self):
print(time.time())
self.__do_while = True
async def go():
# 只 print 了一次就结束了
asyncio.create_task(self.exec())
# 界面卡住了
# await asyncio.create_task(self.exec())
# 界面卡住了
# await self.exec()
asyncio.run(go())
print(time.time())
def stop(self):
self.__do_while = False
async def exec(self):
i = 0
while self.__do_while:
print('exec', i)
i += 1
await asyncio.sleep(2)
if __name__ == "__main__":
Window()
1
ClericPy 2020-05-21 19:27:28 +08:00
你的这个... asyncio.create_task(self.exec()) 得到的是个 asyncio.Task 对象, 你到底要不要阻塞, 我怎么感觉你该做的是总协程丢在外面, window 对象丢到 run_in_executor 里呢...
python3 的 await 如果能自动判断这个关键字后面的是否 awaitable 多好, 现在太麻烦了, 还得自己判断 |
2
ipwx 2020-05-21 19:33:22 +08:00
aio eventloop 需要独占一个线程,GUI 也需要独占一个线程。所以你永远需要至少两个线程。
|
3
gzlock OP @ClericPy #1
我用 Node.js 做了个想要的功能 https://repl.it/@gzlock/ChillyLightblueHashmap 需求就是: 1,不用额外的线程进程啥的(Node.js 那个例子里也没有用到) 2,在异步函数里不断循环 3,在异步函数外部可以中断异步函数里的循环 就目前按我的尝试来说,python 做不到 @ipwx #2 那我用 threading.Thread 算是提前解决问题啦🐶 |
4
renmu123 2020-05-21 21:56:05 +08:00 via Android
我对异步的理解是同时只有一个线程在工作,在休息的时候可以进行调度,因为 while 循环要不停进行工作,主渲染进程自然会卡住,因为只有一个在工作
|
5
Mahaha 2020-05-21 22:19:04 +08:00 via Android
可以试试在 在总入口 if __name__ == "__main__":下面这样子 asyncio.gather(window.xx(), window.exec()) 手机打字大概是这样子
|
6
muzuiget 2020-05-21 22:44:01 +08:00
查查 tkinter 是否有 idle 之类的 callback 接口,或者非阻塞更新。
|
7
imn1 2020-05-21 23:00:00 +08:00
不管是否协程,写 GUI 本身就要避免用 while 无条件循环,想好了退出条件在哪里激活,退出条件在外部激活等于没有条件
其次,先不管能否实现,光看代码,stop()或者主窗口没有任何能接收 async 信号的代码,那它跟协程就没关系,只能等整个协程结束才能工作 |
8
Nich0la5 2020-05-21 23:29:41 +08:00 via Android
Python 协程不是用来做这个的啊,await 之后当前协程会挂起,不知道什么时候才重新拿起来,和游戏的即时性要求不符。
协程主要用于 io 密集型应用而非 CPU 密集型 |
9
ppgs8903 2020-05-22 09:08:02 +08:00
@ipwx 如果这都能问的话,我瞎猜下,PY 调用 C 控制 DMA 设备单独中断 CPU 就好了。aio 看底层对什么设备控制吧,BUF 如果在和 CPU 无关只是给 CPU 一个最高优先级的通知(通知 地址和长度),或者 直接 DMA 和 显示器控制芯片直接互相通信。这就有点飘了,而且不是特别特殊的场景也不这么搞。
我想说的是,这个问题问的就不对,] —— [ 真想学,你看看 PY 开源 UI 库怎么写好了 |
10
ipwx 2020-05-22 09:32:49 +08:00
@ppgs8903 你在说啥?完全牛头不对马嘴吧。。。
@Nich0la5 在 GUI 程序里面用 aio eventloop 没啥问题吧,用啥不能用。关键是楼主用错了。 - - - - @gzlock 要理解 aio,其实最好去看一看 select/epoll 的资料。Python 的 async 语法只是一堆语法糖,本质就是让人写 eventloop 程序更容易。然后 GUI 又是一个典型的 eventloop,有兴趣可以看看 Windows API 写 GUI 的那套,或者看看 Qt 源代码。但不管是什么类型的 eventloop,都需要有一个 while loop 来 receive event -> process event 。 既然有两个 eventloop,那么自然需要两个线程去各自跑这两个 eventloop 。 asyncio.run() 就是跑那么一个 loop,内部我虽然没看过代码,但本质肯定等价于一个 while loop,直到所有协程跑完再退出。如果你在 gui 的 main thread 里面跑,就相当于阻塞了 gui 的 eventloop 。 |
11
wizardoz 2020-05-22 09:59:14 +08:00
你这是需要线程吧
|
12
no1xsyzy 2020-05-22 10:49:47 +08:00
@ipwx #2 GUI 不一定独占线程,只不过独占方式写起来不用烧脑和脏处理。
不过似乎 tkinter 没有处理单轮事件的方式,tk 的 mainloop 好像不在 python 里甚至没触发 GIL 锁?而且实际上 GUI 部分也在不同线程里 mainloop 更像是 join ?没仔细测试。 |
13
ipwx 2020-05-22 11:17:23 +08:00
@no1xsyzy
1 、独占线程和 GIL 锁没有任何关系。一个 C 语言写的线程完全可以进入 while 之前释放 GIL 锁,在调用任何 python 函数之前获取 GIL 锁。tk 的 mainloop 是 C 模块。 搜了一下: https://github.com/python/cpython/blob/master/Modules/_tkinter.c#L2861 对于等待。确实 GUI 一般不是忙等待。像 Windows API 是调用操作系统的 API 获取下一个 event,而操作系统内部必然有队列,不是忙等待。比如 Windows: https://docs.microsoft.com/en-us/windows/win32/learnwin32/window-messages xlib: http://mech.math.msu.su/~nap/2/GWindow/xintro.html mac 没了解过,不过大概差不多,不然很难想象 Qt 那种库该怎么写,因为从上到下都充斥着 event-loop 的味道(比如别的线程要更新界面必须发送一个消息到 GUI 主线程)。所以你见过的不是 event loop 的 GUI 库(比如 Qt )大部分情况下只是给你把操作系统的 eventloop 包装了一下而已。 |
14
no1xsyzy 2020-05-22 11:53:01 +08:00
@ipwx #13 是没有关系,几句话没排版没调序……
协程方式写 GUI,从 Data flow 层面上看是很诡异的,不同 Data flow 还可能发生竞争和歧义。 协程方式写,一个问题就是需要把 asyncio 的 event loop 和 tk 的 mainloop 合并,因为后者似乎不支持任意 task 或者 asyncio 的 Task 的缘故,理应把后者并入前者,也就是把 mainloop 的行为写成一个调用结束事会把自己加进 task 的函数,所以需要单 event 处理,然而似乎没有。 另一个问题就是协程不知道什么时候会被放出来可能导致类似 vb 那样复杂计算放在按钮事件里会导致假死,必须常常 DoEvents 主动释放。 测试时 tk.Tk() 调用完就有空窗口了,而且 REPL 还活着,并且这个窗口也是可以任意改变大小和被关闭的。这两个在 Windows 下都是需要处理 event 的。我不太清楚 tkapp.mainloop 到底干了啥…… |
15
ipwx 2020-05-22 14:59:04 +08:00
@no1xsyzy 我感觉吧,楼主这只是个 demo 。
GUI 调用协程是有实际价值的。譬如你用 requests,用线程池做下载器,并发才多少。用上 aiohttp,并发一下子暴涨。你只需要用 asyncio 的 queue (那个是线程安全的好像)把 task 塞进 asyncio 里面,最后在主线程通过 event 把结果弄回来就行了。 |