通读Python官方文档之协程、Future与Task

Snowflake飘雪
• 阅读 16385

Tasks and coroutines

翻译的python官方文档

这个问题的恶心之处在于,如果你要理解coroutine,你应该理解futuretask。而你如果想理解futuretask你应该先理解coroutine。所以在第一遍阅读官方文档的时候,感觉完全是在梦游。但读到第二遍和第三遍的时候,就清楚很多了。

Coroutines

协程(coroutine)包括两个概念:

  1. 协程函数(async def 或者 @asyncio.coroutine
  2. 协程函数所返回的协程对象。

协程功能:

  1. 通过result = await future或者 result = yeild from future,悬挂协程,直到future完成,获取future的结果/异常(参见下面对futurefuture结果的描述,或等看完future之后回来再阅读这一段)。
  2. 通过 result = await coroutine 或者 result = yeild from coroutine 等待另一个协程的结果(或者异常,异常会被传播)。
  3. returen expression 返回该协程的结果,被await,或者yield from获取。
  4. raise exception,抛出异常,被await,或者yield from获取。

调用协程函数并不能使该协程运行。调用协程函数所返回的协程对象,在被你安排执行之前,不会做任何事情。有两种方式可以启动它:

  1. 通过在一个已经启动的协程中调用:await coroutine或者yield from coroutine
  2. 或者通过ensure_task()以及loop.create_task()安排协程的执行。

只有事件循环在运行的时候,协程才能运行

在本文档中,有些普通函数返回了一个future,也被标记为coroutine。这是故意的,这样以后就可以自由使用这些函数。如果是在回调代码中使用这个函数,用ensure_future包装他。

hello_world.py

import asyncio

#  创建一个协程
async def hello_world():
    print("Hello World!")

loop = asyncio.get_event_loop()
# Blocking call which returns when the hello_world() coroutine is done
# 在事件循环中调用这个协程
# 不过这里只有一个协程,而其不阻塞
loop.run_until_complete(hello_world())
loop.close()

hello_world2.py

# 这段代码和上面的代码执行结果是相同的。只不过用了另一种调用协程的方式
# 先在loop.call_soon()中安排好,再通过loop.run_forever()调用
# 注意,这里在hello_world中,调用了loop.stop(),否则事件循环就不会终止。
import asyncio

def hello_world(loop):
    print('Hello World')
    loop.stop()

loop = asyncio.get_event_loop()

# Schedule a call to hello_world()
loop.call_soon(hello_world, loop)

# Blocking call interrupted by loop.stop()
loop.run_forever()
loop.close()

通读Python官方文档之协程、Future与Task

注意这里return 1+2,实际上是raise StopIteration(3)协程其实是在不停返回结果的。最后的结果才会被返回。

future

future是一个容器,或者占位符(placeholder),用于接受异步的结果。这里指的是asyncio.Future而不是coroutines.futures.Future

接口

result()


返回future的结果

set_result()

指示future已结束,并赋值。注意,必须显式地调用这个接口,才能给future赋值。


import asyncio

# 一个对future进行赋值的函数
async def slow_operation(future):
    await asyncio.sleep(1)
    # 给future赋值
    future.set_result('Future is done!')

loop = asyncio.get_event_loop()
# 创建一个future
future1 = asyncio.Future()
# 使用ensure_future 创建Task
asyncio.ensure_future(slow_operation(future1))
future2 = asyncio.Future()
asyncio.ensure_future(slow_operation(future2))
# gather Tasks,并通过run_uniti_complete来启动、终止loop
loop.run_until_complete(asyncio.gather(future1, future2))
print(future1.result())
print(future2.result())
loop.close()

如果我们注释掉`future.set_result('Future is done!')一行,这个程序将永远不会结束。

TASK

Schedule the execution of a coroutine: wrap it in a future. Task is a subclass of Future.

将一个协程的执行过程安排好:用一个future包装起来。TaskFuture的一个子类。

A task is responsible for executing a coroutine object in an event loop. If the wrapped coroutine yields from a future, the task suspends the execution of the wrapped coroutine and waits for the completion of the future. When the future is done, the execution of the wrapped coroutine restarts with the result or the exception of the future.

Task 负责在实现循环中执行一个协程。 如果被包装的协程由一个future产生,task会暂停被包装协程的执行,等待future的完成。当future完成时,被包装协程会重启,当future结果/异常返回。

Event loops use cooperative scheduling: an event loop only runs one task at a time. Other tasks may run in parallel if other event loops are running in different threads. While a task waits for the completion of a future, the event loop executes a new task.

事件循环使用协同调度:事件循环每次只能执行1个操作。其他task可以在别的线程的事件循环中执行。当task等待future完成时,事件循环会执行一个新的task

The cancellation of a task is different from the cancelation of a future. Calling cancel() will throw a CancelledError to the wrapped coroutine. cancelled() only returns True if the wrapped coroutine did not catch the CancelledError exception, or raised a CancelledError exception.

取消task与取消future不同。调用cancel()将会向被包装的协程抛出CacelledError。如果被包装协程没有捕获CacelledError或者抛出CancelledError时, cancelled()才返回True

这里可以参考Task源码中的一段注释

Request that this task cancel itself.
    This arranges for a CancelledError to be thrown into the
    wrapped coroutine on the next cycle through the event loop.
    The coroutine then has a chance to clean up or even deny
    the request using try/except/finally.
    Unlike Future.cancel, this does not guarantee that the
    task will be cancelled: the exception might be caught and
    acted upon, delaying cancellation of the task or preventing
    cancellation completely.  The task may also return a value or
    raise a different exception.
    Immediately after this method is called, Task.cancelled() will
    not return True (unless the task was already cancelled).  A
    task will be marked as cancelled when the wrapped coroutine
    terminates with a CancelledError exception (even if cancel()
    was not called)
    
    

太长了,我就不翻译了大意就是说,虽然taskcancel()函数,只会向被包装协程发出抛出一个异常,但是task是否真的canceled取决于被包装协程如何处理这个异常。

不要直接创建task实例,使用ensure_future()函数或者loop.create_task()方法。

任务相关函数

asyncio.ensure_future

安排协程的执行。用future包装它,返回一个task。

asyncio.gather(*coros_or_futures, loop=None, return_exceptions=False)

将多个协程或future,集成为一个future。
所有的future必须在一个事件循环中。如果所有的future都成功完成了,则按照输入顺序(而不是返回顺序)返回所有result。

asyncio.sleep(delay, result=None, *, loop=None)

sleep函数,注意,是可以返回结果的






一些参考资料
awesome asyncio


线程和协程

参考这篇文章

线程是操作系统层面的“并行”, 协程是应用程序层面的“并行”。

协程本质上就是:提供一个环境,保存一些需要等待的任务,当这些任务可以执行(等待结束)的时候,能够执行。再等待的过程中,程序可以执行别的任务。

以下内容参考自:PYTHON: GENERATORS, COROUTINES, NATIVE COROUTINES AND ASYNC/AWAIT

@asyncio.coroutine
def foo():
    yield from ....
async def foo():
    await ......

注意在@asyncio.coroutine里只能是 yield from, 在async中,只能是await

你可以通过@type.coroutine装饰器,降一个generator变为一个可await得协程。\


Asynchronous Python

多线程:创建多个线程,每个线程处理一个任务。会竞争资源、死锁什么的。CPU负责切换线程、保存恢复context。


Asyncio Documentation

Asnycio的文档,但是感觉写的一般,有些语焉不详。

引用了一片关于线程的文章,还没看

不用gevent的原因,是因为gevent还是使用了线程,而线程是难以调试的。

Some thoughts on asynchronous API design in a post-async/await world

点赞
收藏
评论区
推荐文章
blmius blmius
4年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
美凌格栋栋酱 美凌格栋栋酱
7个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
Aimerl0 Aimerl0
4年前
Python网络爬虫与信息提取
title:Python网络爬虫与信息提取date:2020121001:00:23tags:Pythoncategories:学习笔记写在前面不知道写啥其实说实话TOC网络爬虫之规则安装requests库cmd命令行打开输入pip3installrequests,等待即可简单测试,爬一下bkjwpythonimportrequ
Stella981 Stella981
3年前
Python3:sqlalchemy对mysql数据库操作,非sql语句
Python3:sqlalchemy对mysql数据库操作,非sql语句python3authorlizmdatetime2018020110:00:00coding:utf8'''
Wesley13 Wesley13
3年前
Java爬虫之JSoup使用教程
title:Java爬虫之JSoup使用教程date:201812248:00:000800update:201812248:00:000800author:mecover:https://imgblog.csdnimg.cn/20181224144920712(https://www.oschin
Stella981 Stella981
3年前
Python3 利用asynico协程系统构建生产消费模型
今天研究了下python3的新特性asynico,试了试aiohttp协程效果,单核QPS在500~600之间,性能还可以。importaiohttpimportasyncioimporthashlibimporttimefromasyncioimportQueue
Stella981 Stella981
3年前
Gevent简明教程
1、前述进程线程协程异步并发编程(不是并行)目前有四种方式:多进程、多线程、协程和异步。多进程编程在python中有类似C的os.fork,更高层封装的有multiprocessing标准库多线程编程python中有Thread和threading异步编程在linux下主要有三种实现selec
Stella981 Stella981
3年前
Python之time模块的时间戳、时间字符串格式化与转换
Python处理时间和时间戳的内置模块就有time,和datetime两个,本文先说time模块。关于时间戳的几个概念时间戳,根据1970年1月1日00:00:00开始按秒计算的偏移量。时间元组(struct_time),包含9个元素。 time.struct_time(tm_y
Easter79 Easter79
3年前
SpringBoot学习:整合shiro自动登录功能(rememberMe记住我功能)
首先在shiro配置类中注入rememberMe管理器!复制代码(https://oscimg.oschina.net/oscnet/675f5689159acfa2c39c91f4df40a00ce0f.gif)/cookie对象;rememberMeCookie()方法是设置Cookie的生成模
Stella981 Stella981
3年前
Python 协程与 Go 协程的区别(二)
👆“Python猫”,一个值得加星标的公众号花下猫语:今天继续分享协程系列的第二篇。万字长文,收藏起来慢慢读吧。PS:原文中有挺多参考链接,微信不能很好保留。故建议阅读原文。作者:lgj\_bky(经作者授权转载)原文:https://www.cnblogs.com/lgjbky/p/10838035.html写在前面
Wesley13 Wesley13
3年前
mysql数据库的查询
1、“查”——之单表查询INSERTINTOstudent2(name,grade,gender)VALUES('songjiang',40,'男'),('wuyong',100,'男'),('qinming',90,'男'),('husanniang',88,'女'),('sunerniang',66,'女'),('wus
Snowflake飘雪
Snowflake飘雪
Lv1
明明都无言以对,还要硬聊,这就是喜欢
文章
4
粉丝
0
获赞
0