我有一段简单的代码使我有些疯狂。几天前,我发布了一个this问题,要求create_task
与input
不兼容。现在我已经找到了与此有关的东西。我在单独的线程中运行事件循环,并在其中推送任务。非常简单的代码。
import asyncio
import threading
async def printer(message):
print(f'[printer] {message}')
def loop_runner(loop):
loop.run_forever()
if __name__ == '__main__':
event_loop = asyncio.get_event_loop()
t = threading.Thread(target=loop_runner, args=(event_loop,))
t.start()
for m in ['hello', 'world', 'foo', 'bar']:
print(f'[loop running ?] {event_loop.is_running()}')
event_loop.create_task(printer(m))
除了这些日志消息外什么也没打印。
[loop running ?] True
[loop running ?] True
[loop running ?] True
[loop running ?] True
现在,如果我阻塞事件循环线程并让它在这样的暂停后运行。
def loop_runner(loop):
time.sleep(1 / 1000)
loop.run_forever()
一切正常,并且打印出来
[loop running ?] False
[loop running ?] False
[loop running ?] False
[loop running ?] False
[printer] hello
[printer] world
[printer] foo
[printer] bar
从表面上看,似乎没有在运行事件循环中创建的任务被执行。但是为什么呢?
我在文档中没有看到与此有关的任何内容。在我在互联网上看到的大多数示例中,人们都在与其他协程循环创建任务并等待它们。但是我认为,如果您不想等待,请在协程之外使用创建任务是合法的。
答案 0 :(得分:3)
从事件循环线程之外创建任务时,需要使用asyncio.run_coroutine_threadsafe
。该函数将以线程安全的方式调度协程,并通知事件循环有新工作要做。它还将返回一个concurrent.futures.Future
对象,您可以使用该对象来阻止当前线程,直到结果可用为止。
从表面上看,似乎没有在运行事件循环中创建的任务被执行。但是为什么呢?
调用create_task
是不够的,因为它不包含“唤醒”事件循环的代码。这是一个功能-通常不需要这种唤醒,添加它只会减慢常规单线程的使用。从事件循环线程调用create_task
时,它位于事件循环回调中,因此一旦完成执行回调,事件循环一旦获得控制权就可以检查其任务队列。但是,当从其他线程调用create_task
时,事件循环正在等待IO处于睡眠状态,因此需要run_coroutine_threadsafe
来唤醒它。
要对此进行测试,您可以创建一个“心跳”协程,该协程仅包含打印某些内容并等待asyncio.sleep(1)
的无限循环。您将看到用create_task
创建的任务与心跳一起执行,这也恰好唤醒了事件循环。在繁忙的asyncio应用程序中,这种效果可能给人以来自另一个线程的create_task
“起作用”的印象。但是,永远不要依赖于此,因为create_task
无法实现正确的锁定,并且可能破坏事件循环内部。
我在文档中没有看到与此有关的任何内容。
答案 1 :(得分:0)
您的代码的行为符合预期:
请注意,调用此(
run_forever()
)会导致我们的主线程阻塞 无限期地。
(https://tutorialedge.net/python/concurrency/asyncio-event-loops-tutorial/#the-run-forever-method)
另请参阅Asyncio How do you use run_forever?了解解决方案(loop.call_soon_threadsafe()
和asyncio.run_coroutine_threadsafe()
)。