在理解asyncio异步模块之前,需要理解yield的使用方法,贴一个例子:
def test():
data = yield "hello"
return data
t = test()
res = t.send(None)
print(res)
try:
t.send('world')
except StopIteration as e:
print(e.value)
例子说明:当生成器首次调用send的方法的时候(参数必须是None),test的函数分成了两部分执行,先响应一个res,即“hello”,再次调用send的时候将“world”传给了data,但此时函数已经无法进行迭代了,故而抛出了StopIteration的错误,并把函数的最终返回值带了回来。python3.4以上版本,支持async/await关键字进行声明,其原理就是如上所示一个yield,就可以调用一次send,否则会抛出StopIteration异常。再贴一个async/await的例子:
async def test1():
print('hello')
async def test():
await test1()
await test1()
try:
test().send(None)
except StopIteration as e:
print(e.value)
从代码上可以看出一个async可以有多个await的关键字,那个await是不是就是用来取代yield的呢?从执行的结果来看,一个async可以调用一次send,但会抛出StopIteration的异常。所以答案自然是否定的了。await后面可以接上可以等待的对象,比如async声明的函数,或者实现__await__方法的类对象。接下来再贴一个来自asyncio模块的Future对象的用法,这里笔者只是作了一点简单的修改,让它看起来更加适合用同步的逻辑去展示。
1、futures.py
from asyncio import exceptions
import events
# States for Future.
_PENDING = 'PENDING'
_CANCELLED = 'CANCELLED'
_FINISHED = 'FINISHED'
class Future:
# Class variables serving as defaults for instance variables.
_state = _PENDING
_result = None
_exception = None
_loop = None
_source_traceback = None
_asyncio_future_blocking = False
__log_traceback = False
def __init__(self, loop=None, coro=None):
if loop is None:
self._loop = events.get_event_loop()
else:
self._loop = loop
if coro:
self._coro = coro
self._callbacks = []
def cancelled(self):
"""Return True if the future was cancelled."""
return self._state == _CANCELLED
# Don't implement running(); see http://bugs.python.org/issue18699
def get_loop(self):
return self._loop
def done(self):
"""Return True if the future is done.
Done means either that a result / exception are available, or that the
future was cancelled.
"""
return self._state != _PENDING
def __schedule_callbacks(self):
"""Internal: Ask the event loop to call all callbacks.
The callbacks are scheduled to be called as soon as possible. Also
clears the callback list.
"""
callbacks = self._callbacks[:]
if not callbacks:
return
self._callbacks[:] = []
for callback in callbacks:
self._loop.call_soon(callback, self)
def set_result(self, result):
"""Mark the future done and set its result.
If the future is already done when this method is called, raises
InvalidStateError.
"""
if self._state != _PENDING:
raise exceptions.InvalidStateError(f'{self._state}: {self!r}')
self._result = result
self._state = _FINISHED
self.__schedule_callbacks()
def result(self):
"""Return the result this future represents.
If the future has been cancelled, raises CancelledError. If the
future's result isn't yet available, raises InvalidStateError. If
the future is done and has an exception set, this exception is raised.
"""
if self._state == _CANCELLED:
raise exceptions.CancelledError
if self._state != _FINISHED:
raise exceptions.InvalidStateError('Result is not ready.')
self.__log_traceback = False
if self._exception is not None:
raise self._exception
return self._result
def add_done_callback(self, callback, *args):
"""Add a callback to be run when the future becomes done.
The callback is called with a single argument - the future object. If
the future is already done when this is called, the callback is
scheduled with call_soon.
"""
if self.done():
self._loop.call_soon(callback, *args)
else:
self._callbacks.append(callback)
def __await__(self):
if not self.done():
self._asyncio_future_blocking = True
yield self # This tells Task to wait for completion.
print(f"await return value")
if not self.done():
raise RuntimeError("await wasn't used with future")
return self.result() # May raise too.
__iter__ = __await__ # make compatible with 'yield from'.
def _get_loop(fut):
# Tries to call Future.get_loop() if it's available.
# Otherwise fallbacks to using the old '_loop' property.
try:
get_loop = fut.get_loop
except AttributeError:
pass
else:
return get_loop()
return fut._loop
async def test():
print("hello...")
async def main():
print("start")
data = await Future(coro=test())
print("over ", data)
return data
按前述例子,一个yield对应一个send,先不管代码中的loop事件,我们来看一下__await__的核心代码:
def __await__(self):
if not self.done():
self._asyncio_future_blocking = True
yield self # This tells Task to wait for completion.
print(f"await return value")
if not self.done():
raise RuntimeError("await wasn't used with future")
return self.result() # May raise too.
至此,我们就应该明白关键字await的作用了吧?它就是用于调用__await__这个函数的特殊方式,那么代码逻辑就变得与第一个例子一致了。我们在看一下执行代码:
if __name__ == '__main__':
fut = main()
try:
result = fut.send(None)
except StopIteration as e:
print(e.value)
else:
try:
result._coro.send(None)
except StopIteration as e:
result.set_result(e.value)
# 返回Future的实例 基于此我们可以写一个基本的循环事件对其函数及其回调函数进行调用
try:
fut.send(None)
except StopIteration as e:
print(e.value)
首先是fut调用了一次send,但是遇到了Future中第一个yield,所以没有抛出StopIteration的异常,而是返回了Future的一个实例,通过对象实例我们可以调用传入Future中的test的函数,而这个函数是没有yield的,所以会抛出一个StopIteration,继而调用result.set_result对任务状态进行一个修改。然后fut再次调用send,main函数已无yield可以阻塞,所以抛出了StopIteration并接收其返回的结果e.value。
基于以上的代码逻辑,我们就可以实现一个简单的loop循环,去添加与执行所有await对象相关的send以及遇到StopIteration(任务结束)后设置值或是回调函数。例如我们可以将main函数修改成如下的伪代码:
async def main():
print("start")
fut = Future()
loop.add_callback(fut.send,*args)
loop.add_callback(callback,*args,fut)
data = await fut
print("over ", data)
return data
之后我们执行loop.run_forever()即可实现所谓的异步交替执行的模式了。
基于上述猜想实现的代码如下(修改自asyncio):
2、events.py
import futures
import collections
class Handle:
_cancelled = False
def __init__(self, callback, loop, *args, ):
self._callback = callback
self._args = args
def _run(self):
self._callback(*self._args)
def cancel(self):
if not self._cancelled:
self._cancelled = True
class BaseEventLoop:
def __init__(self):
self._closed = False
self._stopping = False
self._ready = collections.deque()
def create_future(self):
"""Create a Future object attached to the loop."""
return futures.Future(loop=self)
def stop(self):
self._stopping = True
def run_forever(self):
"""Run the event loop until stop() is called."""
try:
while True:
self._run_once()
if self._stopping:
break
finally:
self._stopping = False
def _run_once(self):
ntodo = len(self._ready)
for i in range(ntodo):
handle = self._ready.popleft()
handle._run()
def _add_callback(self, handle):
"""Add a Handle to _scheduled (TimerHandle) or _ready."""
assert isinstance(handle, Handle), 'A Handle is required here'
if handle._cancelled:
return
assert not isinstance(handle, Handle)
self._ready.append(handle)
def call_soon(self, callback, *args):
handle = Handle(callback, self, *args)
self._ready.append(handle)
loop = BaseEventLoop()
def get_event_loop():
return loop
def _run_until_complete_cb(fut):
futures._get_loop(fut).stop()
BaseEventLoop与普通的类并无不同,只是我们采用了队列的方式,提供了add_callback、call_soon等方法入队列,_run_once的方法出队列并执行其方法,而Handle的实现只是为了统一使用handle.run的方式调用回调函数。
3、tasks.py
import events
import futures
class Task(futures.Future):
def __init__(self, coro, loop=None):
super(Task, self).__init__()
if loop:
self._loop = loop
else:
self._loop = events.get_event_loop()
self._must_cancel = False
self._fut_waiter = None
self._coro = coro
self._loop.call_soon(self.__step)
def cancel(self):
if self.done():
return False
if self._fut_waiter is not None:
if self._fut_waiter.cancel():
# Leave self._fut_waiter; it may be a Task that
# catches and ignores the cancellation so we may have
# to cancel it again later.
return True
# It must be the case that self.__step is already scheduled.
self._must_cancel = True
return True
def __step(self, exc=None):
try:
if exc is None:
result = self._coro.send(None)
else:
result = self._coro.throw(exc) # 有异常,则抛出异常
except StopIteration as exc:
self.set_result(exc.value)
else:
blocking = getattr(result, '_asyncio_future_blocking', None)
# Yielded Future must come from Future.__iter__().
if blocking is not None:
if futures._get_loop(result) is not self._loop:
new_exc = RuntimeError(
f'Task {self!r} got Future '
f'{result!r} attached to a different loop')
self._loop.call_soon(
self.__step, new_exc)
elif blocking:
if result is self:
new_exc = RuntimeError(
f'Task cannot await on itself: {self!r}')
self._loop.call_soon(
self.__step, new_exc)
else:
result._asyncio_future_blocking = False
result.add_done_callback(self.__wakeup, result)
self._fut_waiter = result
if self._must_cancel:
if self._fut_waiter.cancel():
self._must_cancel = False
elif result is None:
# Bare yield relinquishes control for one event loop iteration.
self._loop.call_soon(self.__step)
else:
# Yielding something else is an error.
new_exc = RuntimeError(f'Task got bad yield: {result!r}')
self._loop.call_soon(self.__step, new_exc)
def __wakeup(self, future):
try:
future.result()
except BaseException as exc:
# This may also be a cancellation.
self.__step(exc)
else:
# Don't pass the value of `future.result()` explicitly,
# as `Future.__iter__` and `Future.__await__` don't need it.
# If we call `_step(value, None)` instead of `_step()`,
# Python eval loop would use `.send(value)` method call,
# instead of `__next__()`, which is slower for futures
# that return non-generator iterators from their `__iter__`.
self.__step()
self = None # Needed to break cycles when an exception occurs.
Task继承自Future,因此它拥有__await__的方法,故此可以被await。此外我们另外添加了__step的方法,内容与我们之前同步调用的方式并无多大差异,只是send的方法改成了使用loop来调用了。
4、调用方式
import tasks
import events
async def hello1():
print('hello1...')
await hello3()
print('11111')
await hello2()
print(2222222)
async def hello2():
print('hello2...')
async def hello3():
print('hello3...')
async def main():
task1 = tasks.Task(hello1())
task3 = tasks.Task(hello3())
await task1
await task3
t1 = tasks.Task(main())
t1.add_done_callback(events._run_until_complete_cb) # main函数执行完毕则回调退出循环
loop = events.get_event_loop()
loop.run_forever()
对于整体单线程来说仍然是一个同步,不过对于函数的来说,我们的确实现了阻塞时切换(协程),以上只是将asyncio核心的部分,用整体同步调用的方式去理解去运行的机制。以上如有不足,敬请指教!