我一直在使用Python的asyncio
。我想我现在有一个合理的理解。但是以下行为使我感到困惑。
test.py :
from threading import Thread
import asyncio
async def wait(t):
await asyncio.sleep(t)
print(f'waited {t} sec')
def run(loop):
loop.run_until_complete(wait(2))
loop = asyncio.get_event_loop()
t = Thread(target=run, args=(loop,))
t.start()
loop.run_until_complete(wait(1))
t.join()
此代码是错误的。 我知道。事件循环在运行时无法运行,并且通常也不安全。
我的问题:为什么wait(1)
有时仍能完成其工作?
这是两次连续运行的输出:
>>> py test.py
... Traceback (most recent call last):
... File "test.py", line 14, in <module>
... loop.run_until_complete(wait(1))
... File "C:\Python\Python37\lib\asyncio\base_events.py", line 555, in run_until_complete
... self.run_forever()
... File "C:\Python\Python37\lib\asyncio\base_events.py", line 510, in run_forever
...
... raise RuntimeError('This event loop is already running')
... RuntimeError: This event loop is already running
... waited 2 sec
>>> py test.py
... Traceback (most recent call last):
... File "test.py", line 14, in <module>
... loop.run_until_complete(wait(1))
... File "C:\Python\Python37\lib\asyncio\base_events.py", line 555, in run_until_c
... omplete
... self.run_forever()
... File "C:\Python\Python37\lib\asyncio\base_events.py", line 510, in run_forever
...
... raise RuntimeError('This event loop is already running')
... RuntimeError: This event loop is already running
... waited 1 sec
... waited 2 sec
第一次运行的行为是我所期望的-主线程失败,但是事件循环仍然运行wait(2)
以在线程t
中完成。
第二步令人费解,wait(1)
已经抛出RuntimeError
怎么做?我猜想这与线程同步和事件循环的非线程安全性有关。但是我不知道这是如何工作的。
答案 0 :(得分:1)
每个线程都会抛出异常。。在与事件循环不同的线程中引发运行时错误。无论如何,事件循环都会继续执行。
wait(1)
有时可以完成任务,因为您会很幸运。 asyncio
循环内部数据结构无法防止使用线程导致的竞争情况(这就是为什么应该使用specific thread-support methods的原因)。但是,竞争条件的性质取决于事件的确切顺序,并且每次运行程序时,顺序都可以更改,具体取决于操作系统当时在做什么。
run_until_complete()
方法 first 调用asyncio.ensure_task()
,将协程添加到附加了“完成”回调的任务队列中,该回调将再次停止事件循环,然后调用{ {1}}。当协程返回时,回调将停止循环。 loop.run_forever()
调用会在此处抛出loop.run_forever()
。
从线程执行此操作时,任务将添加到附加到循环的双端队列对象中,如果在适当的时候发生(例如,当运行中的循环不忙于清空队列时),则运行中的循环进入即使RuntimeError
调用引发了异常,主线程也会找到并执行它。
所有这些都依赖于实现细节。不同版本的Python在这里可能会表现出不同的行为,并且如果您安装替代循环(例如uvloop
),几乎肯定还会有不同的行为。
如果要从其他线程安排协程,请使用asyncio.run_coroutine_threadsafe()
;会的:
loop.run_forever()
以上操作实际上并未完成from threading import Thread
import asyncio
async def wait(t):
print(f'going to wait {t} seconds')
await asyncio.sleep(t)
print(f'waited {t} sec')
def run(loop):
asyncio.run_coroutine_threadsafe(wait(2), loop)
loop = asyncio.get_event_loop()
t = Thread(target=run, args=(loop,))
t.start()
loop.run_until_complete(wait(1))
t.join()
协程,因为wait(2)
协程正在与wait(1)
一起运行,因此其回调在2秒等待结束之前再次停止了循环。但是协程实际上是启动的:
loop.run_until_complete()
但是如果您使主线程协程花费的时间更长(例如going to wait 1 seconds
going to wait 2 seconds
waited 1 sec
),那么从线程调度的协程也将完成。在关闭循环之前,您需要做更多的工作以确保不再有计划在循环中运行的未决任务。
答案 1 :(得分:1)
哦...没关系。我阅读了asyncio
的代码,并弄清楚了。实际上很简单。
run_until_complete
调用ensure_future(future, loop=self)
之前会检查self.is_running()
(在run_forever
中完成)。由于循环已经在运行,因此可以在引发RuntimeError
之前拾取任务。当然,并非总是由于比赛条件而发生。