18 Concurrency with asyncio

asyncio: a package that implements concurrency with coroutines driven by an event loop.

18.1 Thread Versus Coroutine: A Comparison

thread version

import threading
import itertools
import time
import sys
class Signal: # 使用go来从外部给线程内发送消息
go = True
def spin(msg, signal):
write, flush = sys.stdout.write, sys.stdout.flush
for char in itertools.cycle('|/-\\'):
status = char + ' ' + msg
write(status)
flush()
write('\x08' * len(status))
time.sleep(1)
if not signal.go:
break
write(' ' * len(status) + '\x08' * len(status))
def slow_function():
# pretend waiting a long time for IO
time.sleep(3)
return 42
def supervisor():
signal = Signal()
# spin函数用另一个线程来跑
spinner = threading.Thread(target=spin, args=('thinking!', signal))
print('spinner object:', spinner) # spinner是一个Thread对象
spinner.start()
result = slow_function()
signal.go = False
spinner.join() # 主进程在这里等待spinner进程结束。
return result
def main():
result = supervisor()
print('Answer:', result)
if __name__ == '__main__':
main()

输出结果是| thinking!,前面是一个spinner,3秒后被清掉。注意sleep会block主进程,但是spinner进程不会被block,照样执行,所以我们能看到旋转的spinner。

leo@localhost py_test $ python3 spinner_thread.py
spinner object: <Thread(Thread-1, initial)>
Answer: 42

注意python没有关闭线程的api,所以这里用了一个signal.go来让线程自己结束。

asyncio version

注意这里的语法和书上的有些出入,主要是@asyncio.coroutine换成了asyncyield from换成了awaitcreate_task()会创建一个Task并run。

注意asyncio.sleeptime.sleep的区别,前者是不会阻塞的,后者会阻塞整个进程(而async是单进程的)。

import asyncio
import itertools
import sys
async def spin(msg):
write, flush = sys.stdout.write, sys.stdout.flush
for char in itertools.cycle('|/-\\'):
status = char + ' ' + msg
write(status)
flush()
write('\x08' * len(status))
try:
await asyncio.sleep(.1)
except asyncio.CancelledError:
break
write(' ' * len(status) + '\x08' * len(status))
async def slow_function():
await asyncio.sleep(3)
return 42
async def supervisor():
spinner = asyncio.create_task(spin('thinking!'))
print('spinner object:', spinner)
result = await slow_function()
spinner.cancel()
return result
def main():
loop = asyncio.get_event_loop()
result = loop.run_until_complete(supervisor())
loop.close()
print('Answer:', result)
if __name__ == '__main__':
main()

结果

spinner object: <Task pending coro=<spin() running at spinner_asyncio.py:6>>
Answer: 42

asyncio协程和线程的比较

  • asyncio.Taskthreading.Thread在功能上大体相当

  • A Task drives a coroutine, a Thread invokes a callable.

  • 不手动实例化Task对象,而是用asyncio相关的方法。

  • 如果get一个Task对象,那这个对象一定是在run的。而Thread对象必须手动调用start方法来run

  • 没有从外部终止线程的api,而Task.cancel()可以终止协程。在协程内部yield的地方触发CancelldError异常。

asyncio.Future: Nonblocking by Design