[英]How to write your own async/awaitable coroutine function in Python?
我正在嘗試編寫自己的 awaiatbale function ,它可以在 asyncio 循環中使用,例如asyncio.sleep()
方法或類似這些可預先實現的方法。
這是我到目前為止所做的:
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()
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
1
10
2
20
3
30
.
.
.
[注意]:
我發現了一種使用生成器的並發/異步方法。 但是,這不是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
出去:
########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
[更新]:
最后,我通過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()
出去:
1
10
2
20
3
30
4
40
5
50
還有另一種並發協程方法,通過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())
出去:
1
10
2
20
3
30
4
40
5
50
通常您不需要編寫低級協程,使用async def
並在其中等待是實現目標的常用方法。
但是,如果您對實現細節感興趣,這里是asyncio.sleep()
的源代碼。
與許多其他低級異步函數類似,它使用 3 個主要內容來實現協程:
loop.call_later()
方法 - 幾個事件循環的方法之一,直接告訴事件循環什么時候做某事async def
和await
- 只是@asyncio.coroutine
的語法糖,並yield from
產生允許將一些 function 轉換為生成器(並“一次執行一步” )這是我對 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())
如果您想 go 低於此值,則必須了解事件循環如何管理生成器(或協程)。 user4815162342提到的視頻 - 是一個很好的起點。
但同樣,以上所有 - 都是實施的細節。 除非您編寫非常非常低級的東西,否則您不必考慮所有這些東西。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.