Python 3.5中async/await的工作机制

多处翻译出于自己理解,如有疑惑请参考原文 原文链接

身为Python核心开发组的成员,我对于这门语言的各种细节充满好奇。尽管我很清楚自己不可能对这门语言做到全知全能,但哪怕是为了能够解决各种issue和参与常规的语言设计工作,我也觉得有必要试着接触和理解Python的内核,弄清楚在底层它是怎么工作的。

话虽如此,直到最近我才理解了Python3.5中async/await的工作机制。在此之前,对于async/await语法,我只知道Python3.3中的yield fromPython3.4中的asyncio让这个新语法得以在Python3.5中实现。由于日常工作中没有接触多少网络编程--asyncio的主要应用领域,虽然它可以做的远不止于此--我对async/await并没有关注太多。以代码来说,我知道:

yield from iterator

(大体)等价于:

from x in iterator:
yield x

而且我知道asyncio是个事件循环的框架,支持异步编程,还有这些术语所表示的(基本)意义。但未曾真正的深入研究async/await语法,分析从最基础的指令到实现代码语法功能的过程,我觉得并没有理解Python中的异步编程,这一点甚至让我心烦意乱。因此我决定花点时间弄明白这个语法的工作机制。鉴于我听到许多人说他们也不理解异步编程的工作机制,我写出了这篇论文(是的,这篇博文耗费时间之长,字数之多,让我妻子把它叫做论文)。

由于我希望对这个语法的工作机制有一个完整的理解,这篇论文中会出现涉及CPython的底层技术细节。如果你不关心这些细节,或者无法通过这篇文章完全理解这些细节--限于篇幅,我不可能详细解释CPython的每个细节,否则这篇文章就要变成一本书了(例如,如果你不知道代码对象具有标识位,那就别在意代码对象是什么,这不是这篇文章的重点)--那也没什么关系。在每个章节的最后,我都添加了一个概念明确的小结,因此如果你对某个章节的内容不感兴趣,那么可以跳过前面的长篇大论,直接阅读结论。

Python中协程(coroutine)的历史

根据维基百科,“协程是将多个低优先级的任务转换成统一类型的子任务,以实现在多个节点之间停止或唤醒程序运行的程序模块”。这句专业论述翻译成通俗易懂的话就是,“协程就是可以人为暂停执行的函数”。如果你觉得,“这听起来像是生成器(generators)”,那么你是对的。

生成器的概念在Python2.2时的PEP 255中(由于实现了遍历器的协议,生成器也被成为生成器遍历器)第一次被引入。主要受到了Icon语言的影响,生成器允许用户创建一个特殊的遍历器,在生成下一个值时,不会占用额外的内存,并且实现方式非常简单(当然,在自定义类中实现__iter__()__next__()方法也可以达到不存储遍历器中所有值的效果,但也带来了额外的工作量)。举例来说,如果你想实现自己的range()函数,最直接的方式是创建一个整数数组:

def eager_range(up_to):
"""创建一个从0到变量up_to的数组,不包括up_to"""
sequence = []
index = []
while index < up_to:
sequence.append(index)
index += 1
return sequence

简单直白,但这个函数的问题是,如果你需要的序列很大,比如0到一百万,你必须创建一个包含了所有整数的长度是一百万的数组。如果使用生成器,你就可以毫不费力的创建一个从0到上限前一个整数的生成器。所占用的内存也只是每次生成的一个整数。

def lazy_range(up_to):
"""一个从0到变量up_to,不包括up_to的生成器"""
index = 0
while index < up_to:
yield index
index += 1

函数可以在遇到yield表达式时暂停执行--尽管yield直到Python2.5才出现--然后在下次被调用时继续执行,这种特性对于节约内存使用有意义深远,可以用于实现无限长度的序列。

也许你已经注意到了,生成器所操作的都是遍历器。多一种更好的创建遍历器的语法的确不错(当你为一个对象定义__iter__()方法作为生成器时,也会收到类似的提升),但如果我们把生成器的“暂停”功能拿出来,再加上“把事物传进去”的功能,Python就有了自己的协程功能(暂且把这个当成Python的一个概念,真正的Python中的协程会在后面详细讨论)。Python 2.5中引入了把对象传进一个被暂停的生成器的功能,这要归功于PEP 342。抛开与本文无关的内容不看,PEP 342引入了生成器的send()方法。这样就不光可以暂停生成器,更可以在生成器停止时给它传回一个值。在上文range()函数的基础上更近一步,你可以让函数产生的序列前进或后退:

def jumping_range(up_to):
"""一个从0到变量up_to,不包括up_to的生成器
传入生成器的值会让序列产生对应的位移
"""
index = 0
while index < up_to:
jump = yield index
if jump is not None:
jump = 1
index += jump if __name__ == '__main__':
iterator = jumping_range(5)
print(next(iterator)) # 0
print(iterator.send(2)) # 2
print(next(iterator)) # 3
print(iterator.send(-1)) # 2
for x in iterator:
print(x) # 3, 4

直到Python 3.3PEP 380引入yield from之前,生成器都没有太大的变化。严格的说,yield from让用户可以轻松便捷的从遍历器(生成器最常见的应用场景)里提取每一个值,进而重构生成器。

def lazy_range(up_to):
"""一个从0到变量up_to,不包括up_to的生成器"""
index = 0
def gratuitous_refactor():
nonlocal index
while index < up_to:
yield index
index += 1
yield from gratuitous_refactor()

同样出于简化重构操作的目的,yield from也支持将生成器串连起来,这样再不同的调用栈之间传递值时,不需要对原有代码做太大的改动。

def bottom():
"""返回yield表达式来允许值通过调用栈进行传递"""
return (yield 42) def middle():
return (yield from bottom()) def top():
return (yield from middle()) # 获取生成器
gen = top()
value = next(gen)
print(value) # Prints '42' try:
value = gen.send(value * 2)
except StopIteration as exc:
print("Error!") # Prints 'Error!'
value = exc.value
print(value) # Prints '84'

总结

Python2.2引入的生成器使代码的执行可以被暂停。而在Python2.5中引入的允许传值给被暂停的生成器的功能,则让Python中协程的概念成为可能。在Python3.3中引入的yield from让重构和连接生成器变得更加简单。

事件循环是什么?

如果你想理解async/await语法,那么理解事件循环的定义,知道它如何支持的异步编程,是不可或缺的基础知识。如果你曾经做过GUI编程--包括网页前端工作--那么你已经接触过事件循环了。但在Python的语言体系中,异步编程的概念还是第一次出现,所以如果不知道事件循环是什么,也情有可原。

让我们回到维基百科,事件循环是“在程序中等待、分发事件或消息的编程结构”。简而言之,事件循环的作用是,“当A发生后,执行B”。最简单的例子可能是每个浏览器中都有的JavaScript事件循环,当你点击网页某处("当A发生后"),点击事件被传递给JavaScript的事件循环,然后事件循环检查网页上该位置是否有注册了处理这次点击事件的onclick回调函数("执行B")。如果注册了回调函数,那么回调函数就会接收点击事件的详细信息,被调用执行。事件循环会不停的收集发生的事件,循环已注册的事件操作来找到对应的操作,因此被称为“循环”。

Python标准库中的asyncio库可以提供事件循环。asyncio在网络编程里的一个重要应用场景,就是以连接到socket的I/O准备好读/写(通过selector模块实现)事件作为事件循环中的“当A发生后”事件。除了GUI和I/O,事件循环也经常在执行多线程或多进程代码时充当调度器(例如协同式多任务处理)。如果你知道Python中的GIL(General Interpreter Lock),事件循环在规避GIL限制方面也有很大的作用。

总结

事件循环提供了一个让你实现“当事件A发生后,执行事件B”功能的循环。简单来说,事件循环监视事件的发生,如果发生的是事件循环关心(“注册”过)的事件,那么事件循环会执行所有被关联到该事件的操作。在Python3.4中加入标准库的asyncio使Python也有了事件循环。

asyncawait是怎么工作的

在Python3.4中的工作方式

在Python3.3推动生成器的发展和Python3.5中事件循环以asyncio的形式出现之间,Python3.4以并发编程的形式实现了异步编程。从本质上说,异步编程就是无法预知执行时间的计算机程序(也就是异步,而非同步)。并发编程的代码即使运行在同一个线程中,执行时也互不干扰(并发不是并行)。例如,以下Python3.4的代码中,并发两个异步的函数调用,每秒递减计数,互不干扰。

import asyncio

# Borrowed from http://curio.readthedocs.org/en/latest/tutorial.html.

def countdown(number, n):
while n > 0:
print('T-minus', n, '({})'.format(number))
yield from asyncio.sleep(1)
n -= 1 loop = asyncio.get_event_loop()
tasks = [
asyncio.ensure_future(countdown('A', 2)),
asyncio.ensure_future(countdown('B', 3))
]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()

在Python3.4中,asyncio.coroutine装饰器被用于修饰使用asyncio库并且作为协程在它的事件循环中运行的函数。这是Python中第一次出现明确的协程定义:一种实装了PEP 342中添加给生成器的方法,基类是抽象类collections.abc.Coroutine的对象。这个定义让那些原本没有异步执行意图的生成器也带上了协程的特征。而为了解决这种混淆,asyncio规定所有作为协程执行的函数都需要以asyncio.coroutine装饰器进行修饰。

有了这样一个明确的协程的定义(同时符合生成器的接口规范),你可以使用yield from将任何asyncio.Future对象传入事件循环,在等待事件发生时暂停程序执行(future对象是asyncio中的一种对象,此处不再详述)。future对象进入事件循环后就处于事件循环的监控之下,一旦future对象完成了自身任务,事件循环就会唤醒原本被暂停的协程继续执行,future对象的返回结果则通过send()方法由事件循环传递给协程。

以上文代码为例,事件循环启动了两个调用call()函数的协程,运行到某个协程中包含yield fromasyncio.sleep()语句处,这条语句将一个asyncio.Future对象返回事件循环,暂停协程的执行。这时事件循环会为future对象等待一秒(并监控其他程序,例如另外一个协程),一秒后事件循环唤醒传出了future对象的被暂停的countdown()协程继续执行,并把future对象的执行结果归还给原协程。这个循环过程会持续到countdown()协程结束执行,事件循环中没有被监控的事件为止。稍后我会用一个完整的例子详细解释协程/事件循环结构的工作流程,但首先,我要解释一下asyncawait是如何工作的。

yield from到Python3.5中的await

在Python3.4中,一个用于异步执行的协程代码会被标记成以下形式:

# 这种写法在Python3.5中同样有效
@asyncio.coroutine
def py34_coro():
yield from stuff()

Python3.5也添加了一个作用和asyncio.coroutine相同,用于修饰协程函数的装饰器types.coroutine。也可以使用async def语法定义协程函数,但是这样定义的协程函数中不能使用yield语句,只允许使用returnawait语句返回数据。

async def py35_coro():
await stuff()

对同一个协程概念添加的不同语法,是为了规范协程的定义。这些陆续补充的语法使协程从抽象的接口变成了具体的对象类型,让普通的生成器和协程用的生成器有了明显的区别(inspect.iscoroutine()方法的判断标准则比async还要严格)。

另外,除了async,Python3.5也引入了await语法(只能在async def定义的函数中使用)。虽然await的使用场景与yield from类似,但是await接收的对象不同。作为由于协程而产生的语法,await接收协程对象简直理所当然。但是当你对某个对象使用await语法时,技术上说,这个对象必须是可等待对象(awaitable object):一种定义了__await__()方法(返回非协程本身的遍历器)的对象。协程本身也被视作可等待对象(体现在Python语言设计中,就是collections.abc.Coroutine继承了collections.abc.Awaitable抽象类)。可等待对象的定义沿用了Python中将大多数语法结构在底层转换成方法调用的传统设计思想,例如a + b等价于a.__add__(b)b.__radd__(a)

那么在编译器层面,yield fromawait的运行机制有什么区别(例如types.coroutine修饰的生成器和async def语法定义的函数)呢?让我们看看上面两个例子在Python3.5环境下执行时的字节码细节有什么不同,py34_coro()执行时的字节码是:

In [31]: dis.dis(py34_coro)
3 0 LOAD_GLOBAL 0 (stuff)
3 CALL_FUNCTION 0 (0 positional, 0 keyword pair)
6 GET_YIELD_FROM_ITER
7 LOAD_CONST 0 (None)
10 YIELD_FROM
11 POP_TOP
12 LOAD_CONST 0 (None)
15 RETURN_VALUE

py35_coro()执行时的字节码是:

In [33]: dis.dis(py35_coro)
2 0 LOAD_GLOBAL 0 (stuff)
3 CALL_FUNCTION 0 (0 positional, 0 keyword pair)
6 GET_AWAITABLE
7 LOAD_CONST 0 (None)
10 YIELD_FROM
11 POP_TOP
12 LOAD_CONST 0 (None)
15 RETURN_VALUE

除了py34_coro代码中多了一行装饰器而导致的行号不同,两组字节码的区别集中在GET_YIELD_FROM_ITER操作符GET_AWAITABLE操作符。两个函数都是以协程的语法声明的。对于GET_YIELD_FROM_ITER,编译器只检查参数是否生成器或者协程,如果不是,就调用iter()函数遍历参数(types.coroutine装饰器修饰了生成器,让代码对象在C代码层面附带了CO_ITERABLE_COROUTINE标识,因此yield from语句可以在协程中接收协程对象)。

GET_AWAITABLE则是另外一番光景。虽然同GET_YIELD_FROM_ITER操作符一样,字节码也接收协程对象,但它不会接收没有协程标记的生成器。而且,正如前文所述,字节码不止接收协程对象,也可以接收可等待对象。这样,yield from语句和await语句都可以实现协程概念,但一个接收的是普通的生成器,另一个是可等待对象。

也许你会好奇,为什么基于async的协程和基于生成器的协程在暂停时接收的对象会不同?这种设计的主要目的是让用户不至于混淆两种类型的协程实现,或者不小心弄错类似的API的参数类型,甚而影响Python最重要的特性的使用体验。例如生成器继承了协程的API,在需要协程时很容易犯使用了普通的生成器的错误。生成器的使用场景不限于通过协程实现流程控制的情况,因此很容易的辨别普通生成器和协程也非常重要。可是,Python不是需要预编译的静态语言,在使用基于生成器的协程时编译器只能做到在运行时进行检查。换句话说,就算使用了types.coroutine装饰器,编译器也无法确定生成器会充当本职工作还是扮演协程的角色(记住,即使代码中明明白白使用了types.coroutine装饰器,依然有在之前的代码中类似types = spam这样的语句存在的可能),编译器会根据已知的信息,在不同的上下文环境下调用不同的操作符。

对于基于生成器的协程和async定义的协程的区别,我的一个非常重要的观点是,只有基于生成器的协程可以真正的暂停程序执行,并把外部对象传入事件循环。当你使用事件循环相关的函数,如asyncio.sleep()时,这些函数与事件循环的交互所用的是框架内部的API,事件循环究竟如何变化,并不需要用户操心,因此也许你很少看到这种关注底层概念的说法。我们大多数人其实并不需要真正实现一个事件循环,而只需要使用async这样的语法来通过事件循环实现某个功能。但如果你像我一样,好奇为什么我们不能使用async协程实现类似asnycio.sleep()的功能,那么答案就在这里。

总结

让我们总结一下这两个相似的术语,使用async def可以定义协程,使用types.coroutine装饰器可以将一个生成器--返回一个不是协程本身的遍历器--声明为协程。await语句只能用于可等待对象(await不能作用于普通的生成器),除此之外就和yield from的功能基本相同。async函数定义的协程中一定会有return语句--包括每个Python函数都有的默认返回语句return None--和/或await语句(不能使用yield语句)。对async函数所添加的限制,是为了保证用户不会混淆它和基于生成器的协程,两者的期望用途差别很大。

请把async/await视为异步编程的API

David Bzazley的Python Brasil 2015 keynote让我发现自己忽略了一件很重要的事。在那个演讲中,David指出,async/await其实是一种异步编程的API(他在Twitter上对我说过同样的话)。我想David的意思是,我们不应该把async/await当成asnycio的一种别名,而应该利用async/await,让asyncio成为异步编程的通用框架。

David对将async/await作为异步编程API的想法深信不疑,甚至在他的curio项目中实现了自己的事件循环。这也侧面证明了Python中async/await作为异步编程语法的作用(不像其他集成了事件循环的语言那样,用户需要自己实现事件循环和底层细节)。async/await语法让像curio这样的项目可以进行不同的底层操作(asyncio使用future对象与事件循环进行交互,而curio使用元祖对象),还让它们可以有不同的侧重和性能优化(为了更广泛的适用性,asyncio实现了完整的传输和协议层框架,而相对简单的curio则需要用户实现那些框架,但也因此获得了更快的运行速度)。

看完了Python中异步编程的(简略)历史,很容易得出async/await == asyncio的结论。我想说的是,asyncio导致了Python3.4中异步编程的出现,并且对Python3.5中async/await的产生居功至伟,但是,async/await的灵活的设计,甚至到了可以不使用asyncio的地步,也不需要为了应用asyncio框架而修改架构。简而言之,async/await语法延续了Python在保证实用性的同时尽可能的让设计灵活的传统。

一个例子

看到这里,你的脑子里应该已经装满了各种新术语和新概念,但对于这些新事物如何实现异步编程却仍一知半解。为了加深理解,以下是一个(略显做作的)异步编程的例子,包括完整的从事件循环到相关业务函数的代码。在这个例子中,协程的用途是实现独立的火箭发射倒计时器,产生的效果是同步进行的倒计时。这是通过异步编程而实现的函数并发,程序执行是有三个协程运行在在同一个线程中,却可以彼此互不干扰。

import datetime
import heapq
import types
import time class Task: """Represent how long a coroutine should wait before starting again. Comparison operators are implemented for use by heapq. Two-item
tuples unfortunately don't work because when the datetime.datetime
instances are equal, comparison falls to the coroutine and they don't
implement comparison methods, triggering an exception. Think of this as being like asyncio.Task/curio.Task.
""" def __init__(self, wait_until, coro):
self.coro = coro
self.waiting_until = wait_until def __eq__(self, other):
return self.waiting_until == other.waiting_until def __lt__(self, other):
return self.waiting_until < other.waiting_until class SleepingLoop: """An event loop focused on delaying execution of coroutines. Think of this as being like asyncio.BaseEventLoop/curio.Kernel.
""" def __init__(self, *coros):
self._new = coros
self._waiting = [] def run_until_complete(self):
# Start all the coroutines.
for coro in self._new:
wait_for = coro.send(None)
heapq.heappush(self._waiting, Task(wait_for, coro))
# Keep running until there is no more work to do.
while self._waiting:
now = datetime.datetime.now()
# Get the coroutine with the soonest resumption time.
task = heapq.heappop(self._waiting)
if now < task.waiting_until:
# We're ahead of schedule; wait until it's time to resume.
delta = task.waiting_until - now
time.sleep(delta.total_seconds())
now = datetime.datetime.now()
try:
# It's time to resume the coroutine.
wait_until = task.coro.send(now)
heapq.heappush(self._waiting, Task(wait_until, task.coro))
except StopIteration:
# The coroutine is done.
pass @types.coroutine
def sleep(seconds):
"""Pause a coroutine for the specified number of seconds. Think of this as being like asyncio.sleep()/curio.sleep().
"""
now = datetime.datetime.now()
wait_until = now + datetime.timedelta(seconds=seconds)
# Make all coroutines on the call stack pause; the need to use `yield`
# necessitates this be generator-based and not an async-based coroutine.
actual = yield wait_until
# Resume the execution stack, sending back how long we actually waited.
return actual - now async def countdown(label, length, *, delay=0):
"""Countdown a launch for `length` seconds, waiting `delay` seconds. This is what a user would typically write.
"""
print(label, 'waiting', delay, 'seconds before starting countdown')
delta = await sleep(delay)
print(label, 'starting after waiting', delta)
while length:
print(label, 'T-minus', length)
waited = await sleep(1)
length -= 1
print(label, 'lift-off!') def main():
"""Start the event loop, counting down 3 separate launches. This is what a user would typically write.
"""
loop = SleepingLoop(countdown('A', 5), countdown('B', 3, delay=2),
countdown('C', 4, delay=1))
start = datetime.datetime.now()
loop.run_until_complete()
print('Total elapsed time is', datetime.datetime.now() - start) if __name__ == '__main__':
main()

正如前文所说,这个例子是有意为之,如果在Python3.5下运行,你会发现虽然三个协程在同一线程中互不干扰,但总运行时间是5秒左右。你可以把TaskSleepingLoopsleep()看成asynciocurio这样生成事件循环的框架提供的接口函数,对普通用户来说,只有countdown()main()函数才需要关注。到此为止,你应该已经明白,asyncawait语句,甚至整个异步编程,都不是完全无法理解的魔术,async/await只是Python为了让异步编程更简便易用而添加的API。

我对未来的愿景

我已经理解了Python中的异步编程,我想把它用到所有地方!这个精巧高效的概念完全可以替代原本线程的作用。问题是,Python3.5和async/await都是面世不久的新事物,这就意味着支持异步编程的库数量不会太多。例如,要发送HTTP请求,你要么手动构造HTTP请求对象(麻烦透顶),然后用一个类似aiohttp的框架把HTTP放进另外的事件循环(对于aiohttp,是asyncio)开始操作;要么就等着哪天出现一个像hyper这样的项目对HTTP这类I/O进行抽象,让你可以使用任意的I/O库(遗憾的是,到目前为止hyper只支持HTTP/2)。

我的个人观点是希望像hyper这样的项目可以继续发展,分离从I/O获取二进制数据和解析二进制数据的逻辑。Python中大部分的I/O库都是包揽进行I/O操作和处理从I/O接收的数据,因此对操作分离进行抽象意义重大。Python标准库的http也存在同样的问题,有处理I/O的连接对象,却没有HTTP解析器。而如果你希望requests库支持异步编程,那么你可能要失望了,因为requests从设计上就是同步编程。拥有异步编程能力让Python社区有机会弥补Python语言中没有多层网络栈抽象的缺点。现在Python的优势是可以像运行同步代码那样运行异步代码,因此填补异步编程空白的工具,可以应用在同步异步两种场景中。

我还希望Python可以增加async协程对yield语句的支持。这可能需要一个新的关键字(也许是anticipate?),但只使用async语法就不能实现事件循环的情况实在不尽人意。幸运的是,在这一点上我不是一个人PEP 492的作者与我观点相同,我觉得这个愿望很有可能成为现实。

总结

总而言之,asyncawait出现的目的就是为了协程,顺便支持可等待对象,也可以把普通生成器转换成协程。所有这些都是为了实现并发操作,来提升Python中的异步编程体验。相比使用多线程的编程体验,协程功能强大并且更为易用--只用了包括注释在内的不到100行代码就实现了一个完整的异步编程实例--兼具良好的适用性和运行效率(curio的FAQ里说它的运行速度比twisted快30-40%,比gevent慢10-15%,别忘了,在Python2+版本中,Twisted用的内存更少而且调试比Go简单,想想我们可以做到什么程度!)。能在Python 3中看到async/await的引入,我非常高兴,并且期待Python社区接纳这个新语法,希望有更多的库和框架支持async/await语法,让所有的Python开发者都可以从异步编程中受益。

*:first-child {
margin-top: 0 !important; }
body > *:last-child {
margin-bottom: 0 !important; }

a {
color: #4183C4; }
a.absent {
color: #cc0000; }
a.anchor {
display: block;
padding-left: 30px;
margin-left: -30px;
cursor: pointer;
position: absolute;
top: 0;
left: 0;
bottom: 0; }

h1, h2, h3, h4, h5, h6 {
margin: 20px 0 10px;
padding: 0;
font-weight: bold;
-webkit-font-smoothing: antialiased;
cursor: text;
position: relative; }

h1:hover a.anchor, h2:hover a.anchor, h3:hover a.anchor, h4:hover a.anchor, h5:hover a.anchor, h6:hover a.anchor {
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA09pVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoMTMuMCAyMDEyMDMwNS5tLjQxNSAyMDEyLzAzLzA1OjIxOjAwOjAwKSAgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6OUM2NjlDQjI4ODBGMTFFMTg1ODlEODNERDJBRjUwQTQiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6OUM2NjlDQjM4ODBGMTFFMTg1ODlEODNERDJBRjUwQTQiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo5QzY2OUNCMDg4MEYxMUUxODU4OUQ4M0REMkFGNTBBNCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo5QzY2OUNCMTg4MEYxMUUxODU4OUQ4M0REMkFGNTBBNCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PsQhXeAAAABfSURBVHjaYvz//z8DJYCRUgMYQAbAMBQIAvEqkBQWXI6sHqwHiwG70TTBxGaiWwjCTGgOUgJiF1J8wMRAIUA34B4Q76HUBelAfJYSA0CuMIEaRP8wGIkGMA54bgQIMACAmkXJi0hKJQAAAABJRU5ErkJggg==) no-repeat 10px center;
text-decoration: none; }

h1 tt, h1 code {
font-size: inherit; }

h2 tt, h2 code {
font-size: inherit; }

h3 tt, h3 code {
font-size: inherit; }

h4 tt, h4 code {
font-size: inherit; }

h5 tt, h5 code {
font-size: inherit; }

h6 tt, h6 code {
font-size: inherit; }

h1 {
font-size: 28px;
color: black; }

h2 {
font-size: 24px;
border-bottom: 1px solid #cccccc;
color: black; }

h3 {
font-size: 18px; }

h4 {
font-size: 16px; }

h5 {
font-size: 14px; }

h6 {
color: #777777;
font-size: 14px; }

p, blockquote, ul, ol, dl, li, table, pre {
margin: 15px 0; }

hr {
background: transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAECAYAAACtBE5DAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBNYWNpbnRvc2giIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6OENDRjNBN0E2NTZBMTFFMEI3QjRBODM4NzJDMjlGNDgiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6OENDRjNBN0I2NTZBMTFFMEI3QjRBODM4NzJDMjlGNDgiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo4Q0NGM0E3ODY1NkExMUUwQjdCNEE4Mzg3MkMyOUY0OCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo4Q0NGM0E3OTY1NkExMUUwQjdCNEE4Mzg3MkMyOUY0OCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PqqezsUAAAAfSURBVHjaYmRABcYwBiM2QSA4y4hNEKYDQxAEAAIMAHNGAzhkPOlYAAAAAElFTkSuQmCC) repeat-x 0 0;
border: 0 none;
color: #cccccc;
height: 4px;
padding: 0;
}

body > h2:first-child {
margin-top: 0;
padding-top: 0; }
body > h1:first-child {
margin-top: 0;
padding-top: 0; }
body > h1:first-child + h2 {
margin-top: 0;
padding-top: 0; }
body > h3:first-child, body > h4:first-child, body > h5:first-child, body > h6:first-child {
margin-top: 0;
padding-top: 0; }

a:first-child h1, a:first-child h2, a:first-child h3, a:first-child h4, a:first-child h5, a:first-child h6 {
margin-top: 0;
padding-top: 0; }

h1 p, h2 p, h3 p, h4 p, h5 p, h6 p {
margin-top: 0; }

li p.first {
display: inline-block; }
li {
margin: 0; }
ul, ol {
padding-left: 30px; }

ul :first-child, ol :first-child {
margin-top: 0; }

dl {
padding: 0; }
dl dt {
font-size: 14px;
font-weight: bold;
font-style: italic;
padding: 0;
margin: 15px 0 5px; }
dl dt:first-child {
padding: 0; }
dl dt > :first-child {
margin-top: 0; }
dl dt > :last-child {
margin-bottom: 0; }
dl dd {
margin: 0 0 15px;
padding: 0 15px; }
dl dd > :first-child {
margin-top: 0; }
dl dd > :last-child {
margin-bottom: 0; }

blockquote {
border-left: 4px solid #dddddd;
padding: 0 15px;
color: #777777; }
blockquote > :first-child {
margin-top: 0; }
blockquote > :last-child {
margin-bottom: 0; }

table {
padding: 0;border-collapse: collapse; }
table tr {
border-top: 1px solid #cccccc;
background-color: white;
margin: 0;
padding: 0; }
table tr:nth-child(2n) {
background-color: #f8f8f8; }
table tr th {
font-weight: bold;
border: 1px solid #cccccc;
margin: 0;
padding: 6px 13px; }
table tr td {
border: 1px solid #cccccc;
margin: 0;
padding: 6px 13px; }
table tr th :first-child, table tr td :first-child {
margin-top: 0; }
table tr th :last-child, table tr td :last-child {
margin-bottom: 0; }

img {
max-width: 100%; }

span.frame {
display: block;
overflow: hidden; }
span.frame > span {
border: 1px solid #dddddd;
display: block;
float: left;
overflow: hidden;
margin: 13px 0 0;
padding: 7px;
width: auto; }
span.frame span img {
display: block;
float: left; }
span.frame span span {
clear: both;
color: #333333;
display: block;
padding: 5px 0 0; }
span.align-center {
display: block;
overflow: hidden;
clear: both; }
span.align-center > span {
display: block;
overflow: hidden;
margin: 13px auto 0;
text-align: center; }
span.align-center span img {
margin: 0 auto;
text-align: center; }
span.align-right {
display: block;
overflow: hidden;
clear: both; }
span.align-right > span {
display: block;
overflow: hidden;
margin: 13px 0 0;
text-align: right; }
span.align-right span img {
margin: 0;
text-align: right; }
span.float-left {
display: block;
margin-right: 13px;
overflow: hidden;
float: left; }
span.float-left span {
margin: 13px 0 0; }
span.float-right {
display: block;
margin-left: 13px;
overflow: hidden;
float: right; }
span.float-right > span {
display: block;
overflow: hidden;
margin: 13px auto 0;
text-align: right; }

code, tt {
margin: 0 2px;
padding: 0 5px;
white-space: nowrap;
border: 1px solid #eaeaea;
background-color: #f8f8f8;
border-radius: 3px; }

pre code {
margin: 0;
padding: 0;
white-space: pre;
border: none;
background: transparent; }

.highlight pre {
background-color: #f8f8f8;
border: 1px solid #cccccc;
font-size: 13px;
line-height: 19px;
overflow: auto;
padding: 6px 10px;
border-radius: 3px; }

pre {
background-color: #f8f8f8;
border: 1px solid #cccccc;
font-size: 13px;
line-height: 19px;
overflow: auto;
padding: 6px 10px;
border-radius: 3px; }
pre code, pre tt {
background-color: transparent;
border: none; }

sup {
font-size: 0.83em;
vertical-align: super;
line-height: 0;
}
* {
-webkit-print-color-adjust: exact;
}
@media screen and (min-width: 914px) {
body {
width: 854px;
margin:0 auto;
}
}
@media print {
table, pre {
page-break-inside: avoid;
}
pre {
word-wrap: break-word;
}
}
-->

[翻译] Python 3.5中async/await的工作机制的更多相关文章

  1. 以Python为例的Async / Await的编程基础

    来源:Redislabs 作者:Loris Cro 翻译:Kevin (公众号:中间件小哥) 近年来,许多编程语言都在努力改进它们的并发原语.Go 语言有 goroutines,Ruby 有 fibe ...

  2. 【转】以Python为例的Async / Await的编程基础

    转, 原文:https://www.cnblogs.com/middleware/p/11996731.html 以Python为例的Async / Await的编程基础 -------------- ...

  3. C#中async/await中的异常处理

    在同步编程中,一旦出现错误就会抛出异常,我们可以使用try-catch来捕捉异常,而未被捕获的异常则会不断向上传递,形成一个简单而统一的错误处理机制.不过对于异步编程来说,异常处理一直是件麻烦的事情, ...

  4. 关于C#中async/await中的异常处理(下)-(转载)

    上一篇文章里我们讨论了某些async/await的用法中出现遗漏异常的情况,并且谈到该如何使用WhenAll辅助方法来避免这种情况.WhenAll辅助方法将会汇总一系列的任务对象,一旦其中某个出错,则 ...

  5. 关于C#中async/await中的异常处理(上)-(转载)

    在同步编程中,一旦出现错误就会抛出异常,我们可以使用try…catch来捕捉异常,而未被捕获的异常则会不断向上传递,形成一个简单而统一的错误处理机制.不过对于异步编程来说,异常处理一直是件麻烦的事情, ...

  6. 关于C#中async/await中的异常处理(上)

    关于C#中async/await中的异常处理(上) 2012-04-11 09:15 by 老赵, 17919 visits 在同步编程中,一旦出现错误就会抛出异常,我们可以使用try…catch来捕 ...

  7. Spark SQL中的Catalyst 的工作机制

      Spark SQL中的Catalyst 的工作机制 答:不管是SQL.Hive SQL还是DataFrame.Dataset触发Action Job的时候,都会经过解析变成unresolved的逻 ...

  8. C# 中 async/await 调用传统 Begin/End 异步方法

    最近在改进园子的图片上传程序,希望实现用户上传图片时同时将图片文件保存在三个地方:1)服务器本地硬盘:2)又拍云:3)阿里云OSS.并且在保存时使用异步操作. 对于异步保存到本地硬盘,只需用 Stea ...

  9. Scrapy(爬虫框架)中,Spider类中parse()方法的工作机制

    parse(self,response):当请求url返回网页没有指定回调函数,默认的Request对象的回调函数,用来处理网页返回的response,和生成的Item或者Request对象 以下分析 ...

随机推荐

  1. 铁乐学python_Day42_线程-信号量事件条件

    铁乐学python_Day42_线程-信号量事件条件 线程中的信号量 同进程的一样,Semaphore管理一个内置的计数器, 每当调用acquire()时内置计数器-1:调用release() 时内置 ...

  2. .NET Reflector注册机激活方法

    .NET Reflector注册机是一款专门针对.NET Reflector(.NET反编译工具软件)而推出的一款破解辅助工具软件.因为官方破解版软件需要118美元才能用,不然只有14天的试用期,为此 ...

  3. [2018HN省队集训D5T1] 沼泽地marshland

    [2018HN省队集训D5T1] 沼泽地marshland 题意 给定一张 \(n\times n\) 的棋盘, 对于位置 \((x,y)\), 若 \(x+y\) 为奇数则可能有一个正权值. 你可以 ...

  4. 用windows自带的fsutil来创建1G稀疏文件(sparse file)

    fsutils file createnew  a.dat 1073741824 fsutil sparse setflag a.dat fsutil sparse setrange a.dat 0  ...

  5. 对于开发WEB方面项目需要的工具和技术了解

    1.IDE:Webstorm,JavaScript 开发工具. 2.版本管理系统:Git,独一无二. 3.单元测试:jsamine,前后端共用.Jasmine是我们梦寐以求的Javascript测试框 ...

  6. Java & Groovy & Scala & Kotlin - 20.Switch 与模式匹配

    Overview 本章主要介绍高级条件语句中的 switch 语句以及其增强版的模式匹配. Java 篇 Switch 特点 Java 中 switch 语句功能类似 if,但是 switch 主要用 ...

  7. VMware三个版本号workstation、server、esxi的差别

    VMware三个版本号 workstation: 单机级.用在个人桌面系统中.须要操作系统支持 servier:工作组级,用于服务器.须要操作系统支持 esxi:企业级,用于服务器,不须要操作系统支持 ...

  8. javascript中对数组对象的深度拷贝

    在前端开发的某些逻辑中,经常需要对现有的js对象创建副本,避免污染原始数据的情况. 如果是简单的一维数组对象,可以使用两个原生方法: 1.splice var arr1 = ['a', 'b', 'c ...

  9. kubernetes 安装学习

    什么是Kubernetes Kubernetes是一个开源平台,用于跨主机群集自动部署,扩展和操作应用程序容器,提供以容器为中心的基础架构. 使用Kubernetes,您可以快速高效地响应客户需求: ...

  10. Python:基础知识(一)

    输入 input():接收命令行下输入 1)在py2下:如果你输的是一串文字,要用引号''或者""引起来,如果是数字则不用. 2)在py3下:相当于py2的raw_input(), ...