【问题标题】:RuntimeWarning: coroutine was never awaited asyncio.run in separate threadRuntimeWarning:协程从未在单独的线程中等待 asyncio.run
【发布时间】:2021-06-16 00:52:34
【问题描述】:

我已经构建了一个基于 aiohttp 的小蜘蛛。以下是一些缩写代码:

import asyncio
from aiohttp import ClientSession
from threading import Thread

class Spider:
    def __init__(self, urls):
        self.urls = urls
        self.start()

    async def fetch(self, session, url):
        async with session.get(url) as response:
            await self.handle_status(response) # undefined here for brevity
            return await self.render_body(response) # undefined here for brevity

    async def process_urls(self):
        async with ClientSession() as session:
            tasks = {self.fetch(session, url) for url in self.urls}
            for task in asyncio.as_completed(tasks):
                raw_data = await task
                data = self.extract_data(*raw_data) # sync method undefined here for brevity
                await self.store_data(data) # undefined here for brevity

    def start(self) -> None:
        try:
            asyncio.run(self.process_urls())
        except RuntimeError:  # loop already running
            x = Thread(target=asyncio.run, args=(self.process_urls(),))
            x.start()
            x.join()

start 方法旨在启动一个 asyncio 循环,但如果一个已经在运行,那么它会在一个新线程中启动一个新循环。

无论是否从现有循环运行,代码都有效。但如果从现有循环运行(例如使用 pytest.mark.asyncio()),我会收到以下警告:

RuntimeWarning:协程 'Spider.process_urls' 从未等待 x.join()

我应该关注这个警告吗?

有没有更好的方法来处理这个不会导致这个警告?

我尝试使用 loop = asyncio.get_running_loop() 和 loop.create_task(self.process_urls()) 而不是创建新线程,但这失败了:

RuntimeError: 协程忽略了 GeneratorExit

我尝试过nest-asyncio,但我的主要用例涉及另一个使用uvloop的库,因此nest-asyncio不兼容。

【问题讨论】:

  • 我认为问题在于,在您的 try 块中,您在引发 RuntimeError 之前创建了协程。所以任务永远不会被取消,因为RuntimeError 在清理运行循环之前在run 内部引发。也许在您的异常处理程序中手动取消您的任务以消除警告。
  • @thisisalsomypassword,我认为这不是问题所在。警告似乎指向 x.join 作为罪魁祸首,而不是 try 块中的 asyncio.run 。此外,try: asyncio.get_running_loop(); except: asyncio.run(); else: Thread 产生指向 x.join() 的相同错误
  • 其实虽然列出了x.join(),但代码行号是x = Thread这一行...
  • RuntimeWarningx.join() 上引发,因为这是代码中循环终止的点。只有这样循环才能知道,您的协程从未被等待。除了这个警告之外,您的程序是否按预期工作?对于被忽略的GeneratorExit,我无话可说。也许完整的追溯会有所帮助。但是,我很确定将新任务附加到正在运行的循环中会比在线程中启动新循环更好。但这取决于你。

标签: python python-asyncio python-multithreading


【解决方案1】:

我承认,我无法为您提供该警告出现的原因的完整解释,但我可以为您提供无警告的代码:

def start(self) -> None:
    try:
        asyncio.get_running_loop()
    except RuntimeError:
        asyncio.run(self.process_urls())
    else:
        # Since the loop is already instantiated we can use it with our own
        # tasks without creating another thread.
        # asyncio.create_task(self.process_urls())
        x = Thread(target=asyncio.run, args=(self.process_urls(),))
        x.start()
        x.join()

您可能会得到RuntimeWarning,因为您的代码在处理“正常物理定律”不适用的异常时创建了新线程(顺便说一句,即使您切换到asyncio.create_task(),也会产生该警告)。但同样,这只是我的猜测,所以持保留态度。

【讨论】:

    猜你喜欢
    • 2018-10-26
    • 2021-10-22
    • 2019-12-15
    • 2021-10-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-10
    • 2020-10-10
    相关资源
    最近更新 更多