简体   繁体   English

Python asyncio - 如何等待并行重试

[英]Python asyncio - how to await for retries in parallel

I'm doing a bunch of async calls in parallel like so:我正在并行执行一堆异步调用,如下所示:

txs = await asyncio.gather(*[fetch_tx_details(s["signature"]) for s in sigs])

These calls can sometimes fail and so I'm decorating each with backoff like so:这些调用有时会失败,所以我像这样用退避来装饰每个调用:

@retry_with_backoff(10)
async def fetch_tx_details(sig):
    # stuff

Where backoff is defined like so:退避定义如下:

def retry_with_backoff(retries=5, backoff_in_ms=100):
    def wrapper(f):
        @functools.wraps(f)
        async def wrapped(*args, **kwargs):
            x = 0
            while True:
                try:
                    return await f(*args, **kwargs)
                except Exception as e:
                    print('Fetch error:', e)

                    if x == retries:
                        raise
                    else:
                        sleep_ms = (backoff_in_ms * 2 ** x +
                                    random.uniform(0, 1))
                        time.sleep(sleep_ms / 1000)
                        x += 1
                        print(f'Retrying {x + 1}/{retries}')

        return wrapped

    return wrapper

The problem I have is that if any of the calls fail, they are retried sequentially rather than in parallel.我遇到的问题是,如果任何调用失败,它们将按顺序而不是并行重试。 Eg I'm trying 1000 calls, 100 fail - I now have 100 sequential calls that are executed (slow) rather than having 100 parallel calls (fast).例如,我正在尝试 1000 次调用,100 次失败 - 我现在有 100 个顺序调用被执行(慢)而不是有 100 个并行调用(快)。

How do I change my code to parallelize retries?如何更改我的代码以并行化重试?

Don't use time.sleep() .不要使用time.sleep() That will completely block execution, including other coroutines.这将完全阻止执行,包括其他协程。 Always use the asyncio.sleep() coroutine in asyncio tasks as that'll yield execution to other tasks that are not blocked:始终在 asyncio 任务中使用asyncio.sleep()协程,因为这将执行其他未被阻止的任务:

sleep() always suspends the current task, allowing other tasks to run. sleep()总是暂停当前任务,让其他任务运行。

You are already using a loop, if you switch to await asyncio.sleep(...) then the task will pause and let others run until the retry wait time is over:你已经在使用一个循环,如果你切换到await asyncio.sleep(...)那么任务将暂停并让其他人运行直到重试等待时间结束:

def retry_with_backoff(retries=5, backoff_in_ms=100):
    def wrapper(f):
        @functools.wraps(f)
        async def wrapped(*args, **kwargs):
            x = 0
            while True:
                try:
                    return await f(*args, **kwargs)
                except Exception as e:
                    print('Fetch error:', e)

                    if x == retries:
                        raise
                    else:
                        sleep_ms = (backoff_in_ms * 2 ** x +
                                    random.uniform(0, 1))
                        await asyncio.sleep(sleep_ms / 1000)
                        x += 1
                        print(f'Retrying {x + 1}/{retries}')

        return wrapped

    return wrapper

Anywhere await is used in a coroutine control can be passed back to the event loop to switch tasks.在协程控件中任何地方使用await都可以传递回事件循环以切换任务。 The connected await calls are the thread from event loop to the current point of execution in asyncio tasks, and the means by which a coroutine can co-operate to allow other tasks to do work every time there is reason to wait for something.连接的await调用是从事件循环到异步任务中当前执行点的线程,以及协程可以协作允许其他任务在每次有理由等待某事时进行工作的方式。 Like waiting for enough time to have passed.就像等待足够的时间过去一样。

time.sleep() , on the other hand, waits without handing control over to other tasks.另一方面, time.sleep()等待而不将控制权移交给其他任务。 Instead, the whole thread is blocked from doing anything, and here that means the asyncio event loop is blocked, and so the other tasks can't execute either.相反,整个线程被阻塞而无法执行任何操作,这意味着 asyncio 事件循环被阻塞,因此其他任务也无法执行。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM