在长时间运行的代码中使用asyncio.sleep()将异步功能划分为多个较小的代码部分,是否很好?

时间:2019-12-13 07:23:50

标签: python asynchronous async-await python-asyncio

如果我具有执行大量计算的功能,并且可能需要一段时间,那么在计算的各个部分之间使用asyncio.sleep()来释放事件循环(防止阻塞事件循环)是否很好?

import asyncio


async def long_function(a, b, c):
    # some calculations
    await asyncio.sleep(0)  # release event loop
    # some another calculations
    await asyncio.sleep(0)  # release event loop

还有另一种更好的方法来解决此类问题吗?也许有一些最佳做法?

1 个答案:

答案 0 :(得分:2)

TL; DR仅使用loop.run_in_executor进行阻止工作


要了解为什么它无济于事,我们首先制作一个class来处理事件循环。喜欢:

class CounterTask(object):
    def __init__(self):
        self.total = 0

    async def count(self):
        while True:
            try:
                self.total += 1
                await asyncio.sleep(0.001)  # Count ~1000 times a second
            except asyncio.CancelledError:
                return

如果事件循环完全打开,则每秒仅会计数约1000次。

天真

为了演示最坏的方法,让我们开始执行计数器任务并天真地运行一个昂贵的函数,而无需考虑后果:

async def long_function1():
    time.sleep(0.2)  # some calculations


async def no_awaiting():
    counter = CounterTask()
    task = asyncio.create_task(counter.count())
    await long_function1()
    task.cancel()
    print("Counted to:", counter.total)


asyncio.run(no_awaiting())

输出:

Counted to: 0

那没有做任何计数!注意,我们从来没有等过。此功能只是在执行同步阻止工作。如果计数器本身能够在事件循环中运行,那我们应该算到那时大约有200个。嗯,所以也许我们将其拆分并利用asyncio将控制权交还给它可以计数的事件循环?让我们尝试一下...

拆分

async def long_function2():
    time.sleep(0.1)  # some calculations
    await asyncio.sleep(0)  # release event loop
    time.sleep(0.1)  # some another calculations
    await asyncio.sleep(0)  # release event loop


async def with_awaiting():
    counter = CounterTask()
    task = asyncio.create_task(counter.count())
    await long_function2()
    task.cancel()
    print("Counted to:", counter.total)


asyncio.run(no_awaiting())

输出:

Counted to: 1

嗯,我想这在技术上会更好。但这最终表明了这一点:asyncio事件循环不应进行任何阻止处理。它并非旨在解决这些问题。事件循环无奈地等待着您的下一个await。但是run_in_executor确实提供了解决方案,同时使我们的代码保持asyncio风格。

执行者

def long_function3():
    time.sleep(0.2)  # some calculations


async def in_executor():
    counter = CounterTask()
    task = asyncio.create_task(counter.count())
    await asyncio.get_running_loop().run_in_executor(None, long_function3)
    task.cancel()
    print("Counted to:", counter.total)


asyncio.run(in_executor())

输出:

Counted to: 164

好多了!通过良好的老式线程方式,我们的循环能够在阻塞函数也执行操作的同时继续进行。