等待异步功能完成

时间:2019-07-27 18:08:23

标签: python python-3.x async-await python-asyncio

我的问题或多或少像this,这实际上是导致this的X-Y问题。但是,这不是重复的,因为我的用例略有不同,并且链接的线程无法回答我的问题。

我正在将一组同步程序从Java移植到Python。这些程序与异步库进行交互。在Java中,我可以阻止并等待该库的异步函数返回一个值,然后使用该值执行操作。

Python似乎在努力阻止我在任何地方阻止任何东西。 await仅可用于async def个函数,而这些函数又必须await个功能。似乎没有内置的方法可以阻止async def / await像病毒一样在我的代码中传播。

TaskFuture没有任何内置的阻塞或wait_until_complete机制,除非我想在Task.done()上循环,这确实很糟糕。 / p>

我尝试了asyncio.get_event_loop().run_until_complete(),但是会产生错误:This event loop is already running. Apparently I'm not supposed to do that for anything except main().

The second linked question above建议使用单独的线程并在其中包装异步函数。我用一些简单的功能对其进行了测试,它似乎可以作为一个通用概念。这里的问题是我的异步库保留了对主线程的事件循环的引用,并在尝试从新线程got Future <Future pending> attached to a different loop引用它时抛出错误。

我考虑过将对异步库的所有引用移动到一个单独的线程中,但是我意识到我仍然无法阻止新线程,因此我必须为该线程创建一个 third 线程。阻止通话,这会使我回到Future attached to a different loop错误。

我在这里几乎没有想法。有没有一种方法可以阻止并等待异步函数返回,或者我真的被迫将整个程序转换为async / await吗? (如果是后者,一个解释会很好。我不明白。)

1 个答案:

答案 0 :(得分:0)

我花了一些时间,但终于找到了实际的问题?

  

是否有一种方法可以阻止并等待异步函数返回,或者我真的被迫将整个程序转换为异步/等待吗?

有一个高级功能asyncio.run()。它做三件事:

  1. 创建新的事件循环
  2. 在该事件循环中运行异步功能
  3. 等待所有未完成的任务并关闭循环

它的源代码在这里:https://github.com/python/cpython/blob/3221a63c69268a9362802371a616f49d522a5c4f/Lib/asyncio/runners.py#L8您会发现它在后台使用了loop.run_until_complete(main)

我想,如果您正在编写完全异步的代码,则应该在asyncio.run()函数末尾的某个地方调用main()。但这不是必须的。您可以在任意位置运行它,多次。注意事项:

  • 在给定线程中,一次只能有一个正在运行的事件循环

  • 不要从async def函数运行它,因为很明显,您已经在运行一个事件循环,因此您可以直接使用await调用该函数

示例:

import asyncio

async def something_async():
    print('something_async start')
    await asyncio.sleep(1)
    print('something_async done')

for i in range(3):
    asyncio.run(something_async())

您可以使多个线程具有自己的事件循环:

import asyncio
import threading

async def something_async():
    print('something_async start in thread:', threading.current_thread())
    await asyncio.sleep(1)
    print('something_async done in thread:', threading.current_thread())

def main():
    t1 = threading.Thread(target=asyncio.run, args=(something_async(), ))
    t2 = threading.Thread(target=asyncio.run, args=(something_async(), ))
    t1.start()
    t2.start()
    t1.join()
    t2.join()

if __name__ == '__main__':
    main()

如果遇到此错误:Future attached to a different loop可能表示以下两种情况:

  1. 您正在使用与另一个事件循环相关的资源,而不是现在正在运行的

  2. 您已经在启动事件循环之前创建了一些资源-在这种情况下,它使用了“默认事件循环”-但是,当您运行asyncio.run()时,会启动一个不同环。我之前遇到过:asyncio.Semaphore RuntimeError: Task got Future attached to a different loop

您需要至少使用Python版本3.5.3-explanation here