Tasks and coroutines
翻译的python官方文档
这个问题的恶心之处在于,如果你要理解coroutine
,你应该理解future
和task
。而你如果想理解future
和task
你应该先理解coroutine
。所以在第一遍阅读官方文档的时候,感觉完全是在梦游。但读到第二遍和第三遍的时候,就清楚很多了。
Coroutines
协程(coroutine
)包括两个概念:
- 协程函数(
async def
或者@asyncio.coroutine
) - 协程函数所返回的协程对象。
协程功能:
- 通过
result = await future
或者result = yeild from future
,悬挂协程,直到future
完成,获取future
的结果/异常(参见下面对future
及future
结果的描述,或等看完future
之后回来再阅读这一段)。 - 通过
result = await coroutine
或者result = yeild from coroutine
等待另一个协程的结果(或者异常,异常会被传播)。 -
returen expression
返回该协程的结果,被await
,或者yield from
获取。 -
raise exception
,抛出异常,被await
,或者yield from
获取。
调用协程函数并不能使该协程运行。调用协程函数所返回的协程对象,在被你安排执行之前,不会做任何事情。有两种方式可以启动它:
- 通过在一个已经启动的协程中调用:
await coroutine
或者yield from coroutine
- 或者通过
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()
注意这里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
包装起来。Task
是Future
的一个子类。
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)
太长了,我就不翻译了大意就是说,虽然task
的cancel()
函数,只会向被包装协程发出抛出一个异常,但是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
得协程。\
多线程:创建多个线程,每个线程处理一个任务。会竞争资源、死锁什么的。CPU负责切换线程、保存恢复context。
Asyncio Documentation
Asnycio的文档,但是感觉写的一般,有些语焉不详。
引用了一片关于线程的文章,还没看
不用gevent
的原因,是因为gevent
还是使用了线程,而线程是难以调试的。
Some thoughts on asynchronous API design in a post-async/await world
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。