python协程

参考链接:

https://docs.python.org/zh-cn/3.7/library/asyncio-task.html

Python 异步编程 | Python 教程 - 盖若

在Python中使用Asyncio系统(3-4)​Task 和 Future - 知乎

线程和协程的区别的通俗说明 - 知乎

Python:协程中 Task 和 Future 的理解及使用 - 简书

https://www.cnblogs.com/micheryu/p/15779377.html

https://www.cnblogs.com/my_life/articles/9087317.html

Python多任务—协程(asyncio详解) 一_asyncio.wait_for-CSDN博客

Python asyncio库核心源码解析_深入理解 python 异步编程 中-CSDN博客

协程及asyncio基础概念

一、

协程(coroutine)也叫微线程,是比线程更小的执行单元,是一种特殊的线程,它可以通过事件循环切换任务,比进程和线程的任务切换效率都高,这是因为进程和线程的任务切换是由操作系统进行的,而协程的任务切换是单纯由代码实现的。

二、

python实现协程主要借助于两个库: asyncio 和 gevent 。 asyncio 是在python3.4时就引入的标准库,直接内置了对协程异步IO的支持。

三、

asyncio 的编程模型本质上是一个消息循环,我们一般先定义一个协程函数, 再通过 asyncio 模块创建事件循环,最后把需要执行的协程任务(或任务列表)扔到事件循环中执行,就实现了异步IO。

四、生活类比

假设有1个洗衣房,里面有10台洗衣机,有1个洗衣工在负责这10台洗衣机。那么1个洗衣房就相当于1个进程,1个洗衣工就相当于1个线程,如果有10个洗衣工,就相当于10个线程,1个进程是可以开多线程的,这就是多线程!洗衣机洗衣服是需要等待时间的,如果有10个洗衣工,1人负责1台洗衣机,这样效率肯定会提高,但是不觉得浪费资源吗?明明1 个人能做的事,却要10个人来做,只是把衣服放进去,打开开关,就没事做了,等衣服洗好再拿出来就可以了,就算很多人来洗衣服,1个人也足以应付,开好第1台洗衣机,在等待的时候去开第2台洗衣机,再开第3台……,直到有洗衣机洗好了,就回来把衣服取出来,接着再取另1台的……(哪台先洗好就先取哪台,所以协程是无序的)。这就是计算机的协程!而洗衣机就是具体的协程函数。

五、async理解

async 用来声明一个函数为异步函数,异步函数的特点是函数能在执行的过程中挂起,去执行其他的异步函数,等到挂起条件(假设挂起条件是asyncio.sleep(5))消失后,(就是5秒后)再回来执行后续代码。

六、await理解

await会把当前协程任务挂起,并把任务控制权交给事件循环,事件循环相当于指挥者角色,指挥下一个协程任务执行。await 用来声明程序挂起,比如异步函数执行到某一步时需要等待的时间很长,就在此时将此异步函数挂起,去执行其他的异步函数。await 后面只能跟可等待对象。假设有两个异步函数a和b,异步函数a中的某一步有await b(),当程序执行到await b()时,当前异步函数a就会挂起去执行另一个异步函数b,就是从函数a内部跳出去执行函数b,当挂起条件消失后,不管函数b是否执行完,都会立马从中跳出来,回到函数a跳出位置继续执行后面的代码。如果await后面跟的函数b不是异步函数,无法在函数b执行的过程中返回,只能等函数b执行完,挂起条件才会消失。如果要在函数b执行完才返回,也就不需要用await关键字了,直接调用函数b就行,所以这就需要await后面跟的是异步函数了。在一个异步函数中,可以不止一次挂起,也就是可以用多个await。

七、[xxx = ]await awaitable/asyncio.wait(fs)

await后必须接awaitable对象(可等待对象)——实现了__await__()方法的对象,主要有三种: Coroutine对象, Task对象 和 Future对象。遇到await,其一,把当前协程任务挂起,等待直到拿到可等待对象的返回结果,才会继续执行后面的代码;其二,把任务控制权交给事件循环。

await前可设置变量接收返回值。当为单任务时,[xxx = ]await awaitable,xxx是协程任务的返回结果;当为多任务时,[xxx = ]await asyncio.wait(fs),xxx设置为done 和 pending接收已完成的协程任务集合和超时未完成的协程任务集合。

八、

(个人看法)create_task方法或ensure_future函数并不会把协程任务添加进事件循环,而是将其排入日程准备执行,因此会出现协程任务未执行完全的现象(如下方示例一);在单任务中,run_until_complete方法或run函数会把日程上的协程任务添加进事件循环;在多任务中,wait协程函数或gather函数收集日程上的协程任务,并添加进事件循环。

asyncio模块部分源码

sleep协程函数源码


'''
async def sleep(delay, result=None, *, loop=None):
    """Coroutine that completes after a given time (in seconds)."""  # coroutine对象在延迟指定时间之后才执行完成
    if delay <= 0:
        await __sleep0()
        return result

    if loop is None:
        loop = events.get_event_loop()
    future = loop.create_future()
    h = loop.call_later(delay,
                        futures._set_result_unless_cancelled,
                        future, result)
    try:
        return await future
    finally:
        h.cancel()
'''

ensure_future函数源码


'''
def ensure_future(coro_or_future, *, loop=None):  # coro_or_future参数:coroutine对象或Future对象
    """Wrap a coroutine or an awaitable in a future.  # 翻译:将一个coroutine对象或可等待对象包装成一个Future对象

    If the argument is a Future, it is returned directly.  # 翻译:如果该参数是一个Future对象,该函数会直接返回该Future对象
    """
    if coroutines.iscoroutine(coro_or_future):  # 判断coro_or_future是否是coroutine对象,如果是
        if loop is None:  # 判断是否创建了事件循环对象,如果否
            loop = events.get_event_loop()  # 创建一个事件循环对象
        task = loop.create_task(coro_or_future) # create_task方法需传入一个coroutine对象,返回一个Task对象
        if task._source_traceback:
            del task._source_traceback[-1]
        return task  # 返回该Task对象
    elif futures.isfuture(coro_or_future):  # 判断coro_or_future是否是Future对象,如果是
        if loop is not None and loop is not futures._get_loop(coro_or_future):
            raise ValueError('loop argument must agree with Future')
        return coro_or_future  # 返回该Future对象
    elif inspect.isawaitable(coro_or_future):  # 判断coro_or_future是否是awaitable对象(可等待对象),如果是
        return ensure_future(_wrap_awaitable(coro_or_future), loop=loop)  # _wrap_awaitable函数会将一个awaitable对象包装成一个coroutine对象,然后递归函数重新执行上方代码
    else:
        raise TypeError('An asyncio.Future, a coroutine or an awaitable is '
                        'required')
'''

create_task方法源码


'''
def create_task(self, coro):
    """Schedule a coroutine object.    # 翻译:传入一个coroutine对象

    Return a task object.  # 翻译:返回一个Task对象
    """
    self._check_closed()
    if self._task_factory is None:
        task = tasks.Task(coro, loop=self)
        if task._source_traceback:
            del task._source_traceback[-1]
    else:
        task = self._task_factory(self, coro)
    return task
'''

wait协程函数源码


'''
async def wait(fs, *, loop=None, timeout=None, return_when=ALL_COMPLETED):
    """Wait for the Futures and coroutines given by fs to complete.  # 翻译:等待由fs(此处为由多个Future对象或coroutine对象组成的序列)提供的Future对象或coroutine对象执行完成

    The sequence futures must not be empty.  # 翻译:Future对象序列不能为空

    Coroutines will be wrapped in Tasks.  # 翻译:coroutine对象将被包装成Task对象

    Returns two sets of Future: (done, pending).  # 翻译:返回两个Future对象集合:(已执行的Future对象的集合,等待执行的Future对象的集合)

    Usage:  # 使用方法

        done, pending = await asyncio.wait(fs)

    Note: This does not raise TimeoutError! Futures that aren't done
    when the timeout occurs are returned in the second set.  # 翻译:当出现超时的情况时,未被执行的Future对象会被传入到第二个集合中
    """
    if futures.isfuture(fs) or coroutines.iscoroutine(fs):
        raise TypeError(f"expect a list of futures, not {type(fs).__name__}")
    if not fs:
        raise ValueError('Set of coroutines/Futures is empty.')
    if return_when not in (FIRST_COMPLETED, FIRST_EXCEPTION, ALL_COMPLETED):
        raise ValueError(f'Invalid return_when value: {return_when}')

    if loop is None:  # 判断是否创建了事件循环对象,如果否
        loop = events.get_event_loop()  # 创建一个事件循环对象

    fs = {ensure_future(f, loop=loop) for f in set(fs)}  # fs变为Future对象或Task对象的集合

    return await _wait(fs, timeout, return_when, loop)  # _wait协程函数返回done, pending两个集合(即已执行的Future对象的集合,等待执行的Future对象的集合),
                                                        # 注意传入的fs必须是一个Future对象的集合,而Task类继承自Future类,所以fs也可以是Task对象的集合
'''

run_until_complete方法源码


'''
def run_until_complete(self, future):
    """Run until the Future is done.  # 翻译:执行事件循环直到Future对象被执行完成

    If the argument is a coroutine, it is wrapped in a Task.  # 翻译:如果future参数是一个coroutine对象,它会被包装成一个Task对象

    WARNING: It would be disastrous to call run_until_complete()
    with the same coroutine twice -- it would wrap it in two
    different Tasks and that can't be good.  # 翻译:警告:连续两次调用run_until_complete()方法处理同一个coroutine对象会完蛋——因为该coroutine对象会被包装成两个不同的Task对象,这样不好

    Return the Future's result, or raise its exception.
    """
    self._check_closed()

    new_task = not futures.isfuture(future)
    future = tasks.ensure_future(future, loop=self)
    if new_task:
        # An exception is raised if the future didn't complete, so there
        # is no need to log the "destroy pending task" message
        future._log_destroy_pending = False

    future.add_done_callback(_run_until_complete_cb)
    try:
        self.run_forever()
    except:
        if new_task and future.done() and not future.cancelled():
            # The coroutine raised a BaseException. Consume the exception
            # to not log a warning, the caller doesn't have access to the
            # local task.
            future.exception()
        raise
    finally:
        future.remove_done_callback(_run_until_complete_cb)
    if not future.done():
        raise RuntimeError('Event loop stopped before Future completed.')

    return future.result()
'''

run函数源码


'''
def run(main, *, debug=False):
    """Execute the coroutine and return the result.  # 执行coroutine对象并返回结果

    This function runs the passed coroutine, taking care of
    managing the asyncio event loop and finalizing asynchronous
    generators.  # 该函数会执行传递过来的coroutine对象,负责管理asyncio事件循环和完成异步生成器。

    This function cannot be called when another asyncio event loop is
    running in the same thread.  # 当另一个asyncio事件循环在同一线程中运行时,该函数无法被调用

    If debug is True, the event loop will be run in debug mode.

    This function always creates a new event loop and closes it at the end.
    It should be used as a main entry point for asyncio programs, and should
    ideally only be called once.  # 该函数总是会创建一个新的事件循环,并在协程任务都结束时关闭它。
                                  # 它应该用作asyncio程序的主要入口点,理想情况下应该只被调用一次。
    Example:

        async def main():
            await asyncio.sleep(1)
            print('hello')

        asyncio.run(main())
    """
    if events._get_running_loop() is not None:
        raise RuntimeError(
            "asyncio.run() cannot be called from a running event loop")

    if not coroutines.iscoroutine(main):
        raise ValueError("a coroutine was expected, got {!r}".format(main))

    loop = events.new_event_loop()
    try:
        events.set_event_loop(loop)
        loop.set_debug(debug)
        return loop.run_until_complete(main)
    finally:
        try:
            _cancel_all_tasks(loop)
            loop.run_until_complete(loop.shutdown_asyncgens())
        finally:
            events.set_event_loop(None)
            loop.close()
'''

定义协程函数、创建协程任务、执行协程任务的演变

python3.4中

python3.4中,协程函数的定义是通过 @asyncio.coroutine 和 yeild from 实现的,协程任务的手动创建是通过ensure_future函数实现,协程任务的执行是通过 run_until_complete 方法实现:


"""
单任务:虽然我们可以把协程函数直接理解为协程任务,但是严格意义上,协程函数并不能称为协程任务,协程函数到协程任务中间有一个过渡——协程任务是对协程对象的封装。
"""
import asyncio
import time

@asyncio.coroutine
def washing():
    print('washer begining')
    yield from asyncio.sleep(3)  # 模拟IO耗时操作
    print('washer finished')

coroutine = washing()
# print(coroutine)  # 这里打印的结果是一个生成器对象,其实已经被装饰器装饰成协程对象
loop = asyncio.get_event_loop()  # 创建一个事件循环对象
print(time.ctime())
loop.run_until_complete(coroutine)  # 通过调用事件循环对象的run_until_complete方法执行协程任务,该方法传入的必须是coroutine对象(或Future对象或Task对象),此时该方法传入的是一个coroutine对象(将Task对象添加到事件循环中)
# loop.run_until_complete(asyncio.ensure_future(coroutine))  # 可运行但不采用,传入run_until_complete方法的是一个被ensure_future函数包装过的Task对象,但是run_until_complete方法本身就可实现将coroutine对象包装成Task对象,多此一举,故不采用
print(time.ctime())
# loop.run_until_complete(asyncio.wait(序列))  # 报错,执行多任务用该行代码,执行单任务用上一行代码

"""
多任务1
"""
import asyncio
import time

@asyncio.coroutine
def washing1():
    print('washer1 begining')
    yield from asyncio.sleep(1)  # 模拟IO耗时操作
    print('washer1 finished')

@asyncio.coroutine
def washing2():
    print('washer2 begining')
    yield from asyncio.sleep(2)
    print('washer2 finished')

@asyncio.coroutine
def washing3():
    print('washer3 begining')
    yield from asyncio.sleep(3)
    print('washer3 finished')

tasks = [washing1(), washing2(), washing3()]
loop = asyncio.get_event_loop()  # 创建一个事件循环对象
print(time.ctime())
loop.run_until_complete(asyncio.wait(tasks))  # wait方法必须传入一个coroutine对象(或Future对象或Task对象)序列,此时该方法传入的是一个Task对象序列(将Task对象都添加到事件循环中)
print(time.ctime())
# loop.run_until_complete(tasks)  # 报错,执行单任务用该行代码,执行多任务用上一行代码

"""
多任务2:与上方多任务1对比:
        问题一:tasks的元素不同设置有什么区别:看wait协程函数源码解读
        问题二:既然可以直接传递coroutine对象,为何要事先用ensure_future函数将coroutine对象包装成Task对象,不多此一举吗:
               如果直接传递coroutine对象,在代码底层,首先会判断当前对象类型,由于是coroutine对象,会将其包装成Task对象,最后返回Task对象;
               而如果传递的是Task对象,首先依然是判断当前对象类型,由于是Task对象,直接返回当前对象。如果当前协程任务有一万个甚至更多,那么两者之间的耗时差距就会出来了
"""
import asyncio
import time

@asyncio.coroutine
def washing1():
    print('washer1 begining')
    yield from asyncio.sleep(1)  # 模拟IO耗时操作
    print('washer1 finished')

@asyncio.coroutine
def washing2():
    print('washer2 begining')
    yield from asyncio.sleep(2)
    print('washer2 finished')

@asyncio.coroutine
def washing3():
    print('washer3 begining')
    yield from asyncio.sleep(3)
    print('washer3 finished')

tasks = [
    asyncio.ensure_future(washing1()),  # ensure_future函数传入的必须是一个coroutine对象(或Future对象或Task对象),此处该函数将一个coroutine对象包装成一个Task对象
    asyncio.ensure_future(washing2()),
    asyncio.ensure_future(washing3())
]
loop = asyncio.get_event_loop()  # 创建一个事件循环对象
print(time.ctime())
loop.run_until_complete(asyncio.wait(tasks))  # 此时wait方法传入的是一个Task对象序列
print(time.ctime())

python3.5后

python3.5后,协程函数的定义还可以(主要)通过 async 和 await 实现的,协程任务的手动创建是通过ensure_future函数实现,协程任务的执行是通过 run_until_complete 方法实现:


"""
单任务
"""
import asyncio
import time

async def washing():  # 定义一个协程函数
    print('washer begining')
    await asyncio.sleep(3)  # 模拟IO耗时操作
    print('washer finished')

coroutine = washing()
loop = asyncio.get_event_loop()  # 创建一个事件循环对象
print(time.ctime())
loop.run_until_complete(coroutine)  # 此时run_until_complete方法传入的是一个coroutine对象
print(time.ctime())

"""
多任务写法1
"""
import asyncio
import time

async def washing1():  # 定义一个协程函数
    print('washer1 begining')
    await asyncio.sleep(1)  # 模拟IO耗时操作
    print('washer1 finished')

async def washing2():
    print('washer2 begining')
    await asyncio.sleep(2)
    print('washer2 finished')

async def washing3():
    print('washer3 begining')
    await asyncio.sleep(3)
    print('washer3 finished')

tasks = [
    asyncio.ensure_future(washing1()),  # 将coroutine对象转变成Task对象
    asyncio.ensure_future(washing2()),
    asyncio.ensure_future(washing3())
]
# tasks = [washing1(), washing2(), washing3()]
loop = asyncio.get_event_loop()  # 创建一个事件循环对象
print(time.ctime())
loop.run_until_complete(asyncio.wait(tasks))  # 此时wait方法传入的是一个Task对象序列
print(time.ctime())

"""
多任务写法2
"""
import asyncio
import time

async def washing1():  # 定义一个协程函数
    print('washer1 begining')
    await asyncio.sleep(1)  # 模拟IO耗时操作
    print('washer1 finished')

async def washing2():
    print('washer2 begining')
    await asyncio.sleep(2)
    print('washer2 finished')

async def washing3():
    print('washer3 begining')
    await asyncio.sleep(3)
    print('washer3 finished')

async def main():
    print('start main:')
    tasks = [
        asyncio.create_task(washing1()),  # 将coroutine对象转变成Task对象
        asyncio.create_task(washing2()),
        asyncio.create_task(washing3())
    ]
    await asyncio.wait(tasks)  # 将Tasks对象都放进事件循环中等待执行
    print('end main:')

if __name__ == '__main__':
    loop = asyncio.get_event_loop()  # 创建一个事件循环对象
    loop.run_until_complete(main())

python3.7后

python3.7后,协程函数的定义主要通过 async 和 await 实现的,协程任务的手动创建还可以(主要)通过create_task方法实现,协程任务的执行还可以(主要)通过 asyncio.run 方法实现:


"""
单任务
"""
import asyncio
import time

async def washing():  # 定义一个协程函数
    print('washer begining')
    await asyncio.sleep(3)  # 模拟IO耗时操作
    print('washer finished')

print(time.ctime())
asyncio.run(washing())  # run方法传入的必须是一个coroutine对象
# asyncio.run(asyncio.ensure_future(washing()))  # 报错
print(time.ctime())

'''
多任务
'''
import asyncio
import time

async def washing1():  # 定义一个协程函数
    print('washer1 begining')
    await asyncio.sleep(1)  # 模拟IO耗时操作
    print('washer1 finished')

async def washing2():
    print('washer2 begining')
    await asyncio.sleep(2)
    print('washer2 finished')

async def washing3():
    print('washer3 begining')
    await asyncio.sleep(3)
    print('washer3 finished')

async def main():
    tasks = [
        asyncio.create_task(washing1()),  # create_task方法传入的必须是一个coroutine对象,并将coroutine对象转变成task对象
        asyncio.create_task(washing2()),
        asyncio.create_task(washing3())
    ]
    # tasks = [
    #     asyncio.ensure_future(washing1()),
    #     asyncio.ensure_future(washing2()),
    #     asyncio.ensure_future(washing3())
    # ]
    # tasks = [washing1(), washing2(), washing3()]
    await asyncio.wait(tasks)

if __name__ == '__main__':
    print(time.ctime())
    asyncio.run(main())  # run方法传入的必须是一个coroutine对象
    print(time.ctime())

大同小异

示例一


import asyncio
import time

async def washing1():
    print('washer1 begining')
    await asyncio.sleep(1)
    print('washer1 finished')

async def washing2():
    print('washer2 begining')
    await asyncio.sleep(2)
    print('washer2 finished')

async def washing3():
    print('washer3 begining')
    await asyncio.sleep(3)
    print('washer3 finished')

async def main():
    print('start main:')
    start_time = time.time()
    task1 = asyncio.create_task(washing1())
    task2 = asyncio.create_task(washing2())
    task3 = asyncio.create_task(washing3())
    await task1  # 打开下方注释,结果各不相同,此时task1、task2、task3并没有放入事件循环
    print('回到main')
#     await task2
#     print('回到main')
#     await task3
#     print('回到main')
    end_time = time.time()
    print('end main')
    print(f'总耗时:{end_time - start_time}')

if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())

示例二


import asyncio

# 协程与协程对象是有区别的:协程对象赋值给变量,协程名称系统可自动默认生成
def callback(future):
    print(type(future))  # <class '_asyncio.Task'>
    print(future)  # <Task finished coro=<foo() done, defined at E: ... xxx.py:11> result=123>
    print(future.result())  # 获取Future对象的返回值
    print(future.get_name())  # 获取Future对象的名称

async def foo():
    print("running")
    return 123

async def main():
    task = asyncio.create_task(foo(), name="foo")  # name形参在python3.8及以上版本可用
    task.add_done_callback(callback)  # 添加回调函数
    await task

if __name__ == '__main__':
    asyncio.run(main())

示例三

gather函数和wait方法的区别:

1、wait 返回两个集合:done和pending,done为已完成的协程任务集合,pending为超时未完成的协程任务集合。需通过task.result()方法获取每个已完成的协程任务返回的结果;而gather返回一个列表,其中包含所有已完成的协程任务的结果,不需要再进行调用或其他操作就可以得到全部结果。

2、gather的返回结果顺序与协程任务传入事件循环时一致,wait的返回结果是无序的。

3、gather具有把普通协程对象包装成协程任务的能力,wait没有。wait只能接收协程任务列表做参数。


'''
协程任务执行结果并没有按任务添加的顺序返回
'''
import asyncio

async def func1(i):
    print(f"协程函数{i}马上开始执行。")
    await asyncio.sleep(2)
    return i

async def main():
    tasks = []
    for i in range(1, 5):
        tasks.append(asyncio.create_task(func1(i)))
    done, pending = await asyncio.wait(tasks)
    print('------', done, '%' * 60, pending, '------')
    for task in done:
        print(f"执行结果: {task.result()}")

if __name__ == '__main__':
    asyncio.run(main())

'''
协程任务执行结果按任务添加的顺序返回
'''
import asyncio
 
async def func1(i):
    print(f"协程函数{i}马上开始执行。")
    await asyncio.sleep(2)
    return i
 
async def main():
    tasks = []
    for i in range(1, 5):
        tasks.append(func1(i))
    results = await asyncio.gather(*tasks)  # 返回一个列表
    print('------', results, type(results)'------')
    for result in results:
        print(f"执行结果: {result}")
 
if __name__ == '__main__':
    asyncio.run(main())

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值