ThreadPoolExecutor和asyncio完成阻塞IO请求
这个小节我们看下如何将线程池和asyncio
结合起来。
在协程里面我们还是需要使用多线程的,那什么时候需要使用多线程呢?
我们知道协程里面是不能加入阻塞IO的,但是有时我们必须执行阻塞IO的操作的时候,我们就需要多线程编程了,即我们要在协程中集成阻塞IO的时候就需要多线程操作。
1 | import asyncio |
上面的代码会生成一个线程池 然后让阻塞的代码去线程池中执行。
看下源码:
1 | def run_in_executor(self, executor, func, *args): |
当我们需要在协程中调用阻塞IO的时候 就可以按照这种方式 放到线程池中
asyncio模拟http请求
在asyncio
里面凡是异步的地方都会创建一个future
1 | import asyncio |
1 | if __name__ == "__main__": |
整个过程和之前我们实现的完全一致
future和task
future
是一个结果容器会将结果放到future
中,结果容器运行完毕之后会运行callback
,类似线程池中的future
。task
是future
的一个子类。
我们看下一个特殊的函数
1 | class Future: |
为什么需要一个Task
对象呢?
实际上task
是协程和future
之间的一个重要桥梁。
我们看下具体代码
我们知道在定义一个协程之后,在驱动协程之前,必须对这个协程调用一次next
或send
方法,让这个协程生效
我们从源码看出task
对象在初始化的时候调用了_step
函数,而这个函数做了两个必要的事情。
第一个就是启动协程:
协程是和线程不一样的,协程必须要经历一个启动的过程。线程则不必,因此线程是由操作系统来调用的。但是协程是程序员自己调度的,我们必须要解决协程启动的问题。所以为了解决这个问题,抽象除了一个task
对象,在初始化的时候就会启动协程。
第二个就是将协程的返回值设置到result
中:
当运行时抛出StopIteration
的时候,就会运行set_result
将协程的return
值保存到result
中。线程中是没有StopIteration
异常的。
为了保持协程和线程接口一致问题,创造了task
对象来解决协程和线程不一样的地方所需要解决的问题。
我们看下上篇的图片,其中将上面的代码图形化了。