asyncio 简单的原理实现(用同步的方式理解异步)

本文深入探讨了Python异步编程的概念,从yield的使用开始,解释了如何通过send方法控制生成器的执行。接着介绍了async/await关键字的引入,它们简化了异步代码的编写,使得异步操作更易读。通过Future对象的示例,展示了如何在asyncio模块中处理异步任务。最后,文章通过一个简单的事件循环模拟,解释了如何使用Future和Task协同工作,实现异步任务的调度。
摘要由CSDN通过智能技术生成

在理解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核心的部分,用整体同步调用的方式去理解去运行的机制。以上如有不足,敬请指教!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值