【问题标题】:asyncio.sleep required after cancelling tasks?取消任务后需要 asyncio.sleep 吗?
【发布时间】:2022-01-24 21:28:09
【问题描述】:

为了测试中间人 tcp 代理,我编写了一个 echo tcp 服务器和一个 tcp 客户端。在每次测试之后,我希望代理和服务器关闭,以确保每次测试都在干净的环境中开始,所以我编写了代码:

class TestConnections(TestCase):

    loop = None

    @classmethod
    def setUpClass(cls) -> None:
        TestConnections.loop = asyncio.new_event_loop()
        asyncio.set_event_loop(TestConnections.loop)

    def setUp(self) -> None:
        EnergyAgent.GP = GrowattParser.GrowattParser()
        self.server = self.loop.create_task(tcp_server(1235))
        self.gp = self.loop.create_task(EnergyAgentProxy(1234, 1235))

    def tearDown(self) -> None:
        logger.debug('Cancelling the tasks')
        self.server.cancel()
        self.gp.cancel()
        self.loop.run_until_complete(asyncio.sleep(0.5))

    @classmethod
    def tearDownClass(cls) -> None:
        logger.debug('Closing the event loop')
        TestConnections.loop.close()

    def test_client2server(self):
        # start the client process, and wait until done
        result = self.loop.run_until_complete(asyncio.wait_for(tcp_client(1235, message), timeout=2))
        self.assertEqual(message, result)

现在,问题是:除非我在方法中添加 tearDown 最后一行

self.loop.run_until_complete(asyncio.sleep(0.5))

我收到关于代理上的任务尚未完成的错误:

ERROR:asyncio:Task 已销毁,但它处于挂起状态!

有什么方法可以让所有的任务都完成吗?我试过跑步

self.loop.run_until_complete(asyncio.wait([self.server, self.gp]))

和 asyncio.gather... 不成功。

【问题讨论】:

    标签: python python-asyncio coroutine terminate


    【解决方案1】:

    您正在手动管理您的事件循环,但在退出之前没有等待计划任务完成,并且同步拆卸永远不会自行调用事件循环。因此,当 Python 退出并强制删除它们时,这些任务仍在运行。

    我建议使用unittest.IsolatedAsyncioTestCase 而不是自己管理它,这将在退出测试用例时适当地取消并等待剩余的任务。

    上面的代码看起来像这样

    class TestConnections(unittest.IsolatedAsyncioTestCase):
    
        async def asyncSetUp(self) -> None:
            EnergyAgent.GP = GrowattParser.GrowattParser()
            self.server = asyncio.create_task(tcp_server(1235))
            self.gp = asyncio.create_task(EnergyAgentProxy(1234, 1235))
    
        async def test_client2server(self):
            # start the client process, and wait until done
            result = await asyncio.wait_for(tcp_client(1235, message), timeout=2)
            self.assertEqual(message, result)
    

    如果为每个测试用例创建新的事件循环是一个问题,您可以在关闭之前自己清理事件循环,方法是通过asyncio.all_tasks 获取所有任务,取消它们,等待它们完成执行(被取消或引发异常)通过run_until_complete,然后运行loop.shutdown_asyncgens

    【讨论】:

    • 优秀的回答和解释:非常感谢!
    【解决方案2】:

    我假设正确的实例tearDown 方法,类tearDownClass 被调用?

    当您在任务完成之前关闭事件循环时,通常会出现该错误。当您取消任务时,它会立即返回,但任务本身尚未取消,而是等待循环将其抛出CancelledError。所以你应该await 让任务接收它的终止错误。如果您在任务有机会被抛出之前关闭事件循环,它不会被视为已取消,您将收到该错误。

    通过拨打self.loop.run_until_complete(asyncio.sleep(0.5)) 电话,您实际上是以非故意的方式这样做的。

    文档elaborate about that 也结帐this answer

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-01-25
      • 2012-06-12
      • 2019-04-08
      • 1970-01-01
      • 2013-12-26
      • 1970-01-01
      相关资源
      最近更新 更多