将任务添加到不同线程中的空循环时的奇怪行为
Strange behaviour when task added to empty loop in different thread
我有一个应用程序可以将协程添加到已经 运行 的事件循环中。
这些协程的参数取决于 I/O 并且在我最初启动事件循环时不可用 - 使用 loop.run_forever(),所以我稍后添加任务。为了演示这种现象,这里有一些示例代码:
import asyncio
from threading import Thread
from time import sleep
loop = asyncio.new_event_loop()
def foo():
loop.run_forever()
async def bar(s):
while True:
await asyncio.sleep(1)
print("s")
#loop.create_task(bar("A task created before thread created & before loop started"))
t = Thread(target=foo)
t.start()
sleep(1)
loop.create_task(bar("secondary task"))
奇怪的是,当调用 loop.run_forever() 时,如果循环中至少有一个任务,一切都会按预期进行。即当注释行未被注释掉时。
但是当它被注释掉时,如上所示,没有打印任何内容,看来我无法向 event_loop 添加任务。我应该避免在不添加单个任务的情况下调用 run_forever() 吗?我不明白为什么这应该是个问题。在 运行 之后向 event_loop 添加任务是标准的,为什么空案例会成为问题?
Adding tasks to an event_loop after it is running is standard, why should the empty case be an issue?
因为您应该从线程 运行 事件循环 添加任务 。一般来说,不应混合使用线程和 asyncio,除非通过为此目的设计的 API,例如 loop.run_in_executor
.
如果您理解这一点并且仍然有充分的理由从单独的线程添加任务,请使用 asyncio.run_coroutine_threadsafe
。将 loop.create_task(bar(...))
更改为:
asyncio.run_coroutine_threadsafe(bar("in loop"), loop=loop)
run_coroutine_threadsafe
以thread-safe的方式访问事件循环,同时确保事件循环唤醒注意到新任务,即使否则它无事可做,只是在等待 IO/timeouts.
事先添加另一个任务似乎只是工作,因为 bar
恰好是一个无限协程,它使事件循环每秒都被唤醒。一旦事件循环因任何原因被唤醒,它就会执行所有可运行的任务,而不管它们是哪个线程添加的。但是,依赖这个是一个非常糟糕的主意,因为 loop.create_task
不是 thread-safe,所以如果它与 运行 事件并行执行,可能会有任意数量的竞争条件循环。
因为loop.create_task不是线程安全的,如果你设置loop._debug = True
,你应该看到错误为
Traceback (most recent call last):
File "test.py", line 23, in <module>
loop.create_task(bar("secondary task"))
File "/Users/soulomoon/.pyenv/versions/3.6.3/lib/python3.6/asyncio/base_events.py", line 284, in create_task
task = tasks.Task(coro, loop=self)
File "/Users/soulomoon/.pyenv/versions/3.6.3/lib/python3.6/asyncio/base_events.py", line 576, in call_soon
self._check_thread()
File "/Users/soulomoon/.pyenv/versions/3.6.3/lib/python3.6/asyncio/base_events.py", line 615, in _check_thread
"Non-thread-safe operation invoked on an event loop other "
RuntimeError: Non-thread-safe operation invoked on an event loop other than the current one
我有一个应用程序可以将协程添加到已经 运行 的事件循环中。 这些协程的参数取决于 I/O 并且在我最初启动事件循环时不可用 - 使用 loop.run_forever(),所以我稍后添加任务。为了演示这种现象,这里有一些示例代码:
import asyncio
from threading import Thread
from time import sleep
loop = asyncio.new_event_loop()
def foo():
loop.run_forever()
async def bar(s):
while True:
await asyncio.sleep(1)
print("s")
#loop.create_task(bar("A task created before thread created & before loop started"))
t = Thread(target=foo)
t.start()
sleep(1)
loop.create_task(bar("secondary task"))
奇怪的是,当调用 loop.run_forever() 时,如果循环中至少有一个任务,一切都会按预期进行。即当注释行未被注释掉时。
但是当它被注释掉时,如上所示,没有打印任何内容,看来我无法向 event_loop 添加任务。我应该避免在不添加单个任务的情况下调用 run_forever() 吗?我不明白为什么这应该是个问题。在 运行 之后向 event_loop 添加任务是标准的,为什么空案例会成为问题?
Adding tasks to an event_loop after it is running is standard, why should the empty case be an issue?
因为您应该从线程 运行 事件循环 添加任务 。一般来说,不应混合使用线程和 asyncio,除非通过为此目的设计的 API,例如 loop.run_in_executor
.
如果您理解这一点并且仍然有充分的理由从单独的线程添加任务,请使用 asyncio.run_coroutine_threadsafe
。将 loop.create_task(bar(...))
更改为:
asyncio.run_coroutine_threadsafe(bar("in loop"), loop=loop)
run_coroutine_threadsafe
以thread-safe的方式访问事件循环,同时确保事件循环唤醒注意到新任务,即使否则它无事可做,只是在等待 IO/timeouts.
事先添加另一个任务似乎只是工作,因为 bar
恰好是一个无限协程,它使事件循环每秒都被唤醒。一旦事件循环因任何原因被唤醒,它就会执行所有可运行的任务,而不管它们是哪个线程添加的。但是,依赖这个是一个非常糟糕的主意,因为 loop.create_task
不是 thread-safe,所以如果它与 运行 事件并行执行,可能会有任意数量的竞争条件循环。
因为loop.create_task不是线程安全的,如果你设置loop._debug = True
,你应该看到错误为
Traceback (most recent call last):
File "test.py", line 23, in <module>
loop.create_task(bar("secondary task"))
File "/Users/soulomoon/.pyenv/versions/3.6.3/lib/python3.6/asyncio/base_events.py", line 284, in create_task
task = tasks.Task(coro, loop=self)
File "/Users/soulomoon/.pyenv/versions/3.6.3/lib/python3.6/asyncio/base_events.py", line 576, in call_soon
self._check_thread()
File "/Users/soulomoon/.pyenv/versions/3.6.3/lib/python3.6/asyncio/base_events.py", line 615, in _check_thread
"Non-thread-safe operation invoked on an event loop other "
RuntimeError: Non-thread-safe operation invoked on an event loop other than the current one