简体   繁体   English

如何在 Python 中编写自己的异步/等待协程 function?

[英]How to write your own async/awaitable coroutine function in Python?

I'm trying to write my own awaiatbale function which could use in asyncio loop such as asyncio.sleep() method or something like these pre-awaitable implemented methods.我正在尝试编写自己的 awaiatbale function ,它可以在 asyncio 循环中使用,例如asyncio.sleep()方法或类似这些可预先实现的方法。

Here is what I've done so far:这是我到目前为止所做的:

import asyncio

def coro1():
    for i in range(1, 10):
        yield i

def coro2():
    for i in range(1, 10):
        yield i*10

class Coro:  # Not used.
    def __await__(self):
        for i in range(1, 10):
            yield i * 100

@asyncio.coroutine
def wrapper1():
    return (yield from coro1())

@asyncio.coroutine
def wrapper2():
    return (yield from coro2())

for i in wrapper1():
    print(i)

print("Above result was obvious which I can iterate around a couroutine.".center(80, "#"))

async def async_wrapper():
    await wrapper1()
    await wrapper2()

loop = asyncio.get_event_loop()
futures = [asyncio.ensure_future(async_wrapper())]
result = loop.run_until_complete(asyncio.gather(*futures))
print(result)

loop.close()

What I got as a result:结果我得到了什么:

1
2
3
4
5
6
7
8
9
#######Above result was obvious which I can iterate around a couroutine.#########
Traceback (most recent call last):
  File "stack-coroutine.py", line 36, in <module>
    result = loop.run_until_complete(asyncio.gather(*futures))
  File "/usr/lib/python3.6/asyncio/base_events.py", line 484, in run_until_complete
    return future.result()
  File "stack-coroutine.py", line 30, in async_wrapper
    await wrapper1()
  File "stack-coroutine.py", line 18, in wrapper1
    return (yield from coro1())
  File "stack-coroutine.py", line 5, in coro1
    yield i
RuntimeError: Task got bad yield: 1

What I expect as a result:我期望的结果是:

1
10
2
20
3
30
.
.
.

[ NOTE ]: [注意]:

  • I'm not looking for a multithread or multiprocess method.我不是在寻找多线程或多进程方法。
  • This Question is almost similar to my question which has not resolved yet.这个问题与我尚未解决的问题几乎相似。
  • I'm using Python3.6我正在使用Python3.6

I found a concurrency/asynchronous approach using generators.我发现了一种使用生成器的并发/异步方法。 However, it's not an asyncio approach:但是,这不是asyncio方法:

from collections import deque

def coro1():
    for i in range(1, 5):
        yield i

def coro2():
    for i in range(1, 5):
        yield i*10

print('Async behaviour using default list with O(n)'.center(60, '#'))
tasks = list()
tasks.extend([coro1(), coro2()])

while tasks:
    task = tasks.pop(0)
    try:
        print(next(task))
        tasks.append(task)
    except StopIteration:
        pass

print('Async behaviour using deque with O(1)'.center(60, '#'))
tasks = deque()
tasks.extend([coro1(), coro2()])

while tasks:
    task = tasks.popleft()  # select and remove a task (coro1/coro2).
    try:
        print(next(task))
        tasks.append(task)  # add the removed task (coro1/coro2) for permutation.
    except StopIteration:
        pass

Out:出去:

########Async behaviour using default list with O(n)########
1
10
2
20
3
30
4
40
###########Async behaviour using deque with O(1)############
1
10
2
20
3
30
4
40

[UPDATE]: [更新]:

Finally, I've solved this example through asyncio syntax:最后,我通过asyncio语法解决了这个例子:

import asyncio

async def coro1():
    for i in range(1, 6):
        print(i)
        await asyncio.sleep(0)  # switches task every one iteration.

async def coro2():
    for i in range(1, 6):
        print(i * 10)
        await asyncio.sleep(0)  # switches task every one iteration.

loop = asyncio.get_event_loop()
futures = [
    asyncio.ensure_future(coro1()),
    asyncio.ensure_future(coro2())
]
loop.run_until_complete(asyncio.gather(*futures))
loop.close()

Out:出去:

1
10
2
20
3
30
4
40
5
50

And another concurrency coroutine approach via async-await expression and an event-loop manager based on Heap queue algorithm , without using asyncio library and its event-loop as well as without using asyncio.sleep() method:还有另一种并发协程方法,通过async-await表达式和基于堆队列算法的事件循环管理器,不使用asyncio库及其事件循环以及不使用asyncio.sleep()方法:

import heapq
from time import sleep
from datetime import datetime, timedelta

class Sleep:
    def __init__(self, seconds):
        self.sleep_until = datetime.now() + timedelta(seconds=seconds)

    def __await__(self):
        yield self.sleep_until

async def coro1():
    for i in range(1, 6):
        await Sleep(0)
        print(i)

async def coro2():
    for i in range(1, 6):
        await Sleep(0)
        print(i * 10)

def coro_manager(*coros):
    coros = [(datetime.now(), coro) for coro in coros]
    heapq.heapify(coros)
    while coros:
        exec_at, coro = heapq.heappop(coros)
        if exec_at > datetime.now():
            sleep((exec_at - datetime.now()).total_seconds())
        try:
            heapq.heappush(coros, (coro.send(None), coro))
        except StopIteration:
            try:
                coros.remove(coro)
            except ValueError:
                pass

coro_manager(coro1(), coro2())

Out:出去:

1
10
2
20
3
30
4
40
5
50

Usually you don't need to write low-level coroutines, using async def and awaiting inside it is a common way to achieve your goal.通常您不需要编写低级协程,使用async def并在其中等待是实现目标的常用方法。

However if you interested in implementation details here's source code of asyncio.sleep() .但是,如果您对实现细节感兴趣,这里是asyncio.sleep()源代码

Similar to many other low-level asyncio functions it uses 3 main things to implement coroutine:与许多其他低级异步函数类似,它使用 3 个主要内容来实现协程:

  • asyncio.Future() - "a bridge" between callbacks-world and coroutines-world asyncio.Future() - 回调世界和协程世界之间的“桥梁”
  • event loop's loop.call_later() method - on of several event loop's methods that tells directly to event loop when to do something事件循环的loop.call_later()方法 - 几个事件循环的方法之一,直接告诉事件循环什么时候做某事
  • async def and await - just a syntax sugar for @asyncio.coroutine and yield from that allows to cast some function to generator (and execute it "one step at the time" ) async defawait - 只是@asyncio.coroutine语法糖,并yield from产生允许将一些 function 转换为生成器(并“一次执行一步”

Here's my rough implementation of sleep that shows the idea:这是我对 sleep 的粗略实现,它显示了这个想法:

import asyncio


# @asyncio.coroutine - for better tracebacks and edge cases, we can avoid it here
def my_sleep(delay):
    fut = asyncio.Future()

    loop = asyncio.get_event_loop()
    loop.call_later(
        delay,
        lambda *_: fut.set_result(True)
    )

    res = yield from fut
    return res


# Test:
@asyncio.coroutine
def main():
    yield from my_sleep(3)
    print('ok')


asyncio.run(main())

If you want to go lower than this you'll have to comprehend how generators (or coroutines) being managed by event loop.如果您想 go 低于此值,则必须了解事件循环如何管理生成器(或协程)。 Video mentioned by user4815162342 - is a good place to start. user4815162342提到的视频 - 是一个很好的起点。

But again, all above - are details of implementation.但同样,以上所有 - 都是实施的细节。 You don't have to think about all this stuff unless you write something very-very low-level.除非您编写非常非常低级的东西,否则您不必考虑所有这些东西。

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

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