在写代码的时候又遇到一个问题,想要结合asyncio和多线层,结果可能是因为使用python的版本比较低(python3.6.8),不能运行asyncio.run方法,总之就是想要的目的没有达到!
然后开始百度啥的,没找到问题原因及解决办法,最后觉得还是要看一下python异步编程的原理啥的,然后各种搜之后,又被我发现一个大佬写的文章,在这里记录一下文章链接,方便以后看。也给需要的你们安利一下!!!再次感谢大佬,以及希望大佬早日出下篇!!!
关于上的个人记录
4.4 基于生成器的协程
由于我太菜了,在看上篇中的《4.4 基于生成器的协程》部分时,看不懂,然后就把文章中的代码写下来运行看结果,然后看了好久,再结合大佬的解释,好像才开始看懂了(꒦_꒦)
开始我通过debug,但是还是不太清楚代码运行过程,最后通过在每个地方输出打印,再结合大佬的说明,最后看懂了,所以一开始看不懂就多看几遍,通过仔细看每一条输出语句最后应该能搞明白
关于生成器的基础,可以看一下我的另一篇:Python 可迭代对象、迭代器、生成器
import socket
from selectors import DefaultSelector, EVENT_READ, EVENT_WRITE
selector = DefaultSelector()
# 这里只写一个,方便研究代码运行
urls_todo = [0]
stopped = False
class Future:
"""
保存异步调用的结果和回调
"""
def __init__(self):
print('future __init__')
self.result = None # 异步调用的结果
self._callbacks = [] # 异步调佣的回调函数
def add_done_callback(self, fn):
print('future add_done_callback')
self._callbacks.append(fn)
def set_result(self, result):
print('future set_result')
self.result = result
for fn in self._callbacks:
# self 代表Future实例对象
# 因为这里的回调函数fn都是Task类中的step()
# 而step()需要传递Future()作为参数
# 所以这里的self表示的是step中的future
fn(self)
class Crawler:
def __init__(self, _url):
print('crawler __init__')
self.url = _url
self.response = b''
def fetch(self):
print('crawler fetch')
sock = socket.socket()
sock.setblocking(False) # 设置为不阻塞
try:
print('crawler connect')
sock.connect(('www.baidu.com', 80))
except BlockingIOError:
pass
# f1 存放sock.connect(('www.baidu.com', 80))的结果和回调
# 即f1是存放socket网络连接异步调用的结果和回调
# 因为网络连接不需要存储返回的数据,所以在on_connected方法中设置为None,f1.set_result(None)
f1 = Future()
# 这里的回调on_connected和下面的on_readable都是实际都是为了调用生成器fetch
def on_connected():
print('crawler on_connected, f1=', f1)
f1.set_result(None)
# 通过selector.register注册可写事件,当网络连接成功时执行回调on_connected
selector.register(sock.fileno(), EVENT_WRITE, on_connected)
print('crawler yield f1, f1=', f1)
yield f1
selector.unregister(sock.fileno())
get = 'GET http://www.baidu.com/ HTTP/1.1' # 请求地址
sock.send(get.encode('ascii'))
print('crawler send socket')
global stopped
while True:
# f2存储服务器返回的结果和返回结果之后的回调
f2 = Future()
def on_readable():
print('crawler on_readable, f2=', f2)
f2.set_result(sock.recv(1024))
selector.register(sock.fileno(), EVENT_READ, on_readable)
print('crawler yield f2 chunk before, f2=', f2)
chunk = yield f2
print('crawler yield f2 chunk after, f2=', f2)
print('crawler chunk=', chunk)
selector.unregister(sock.fileno())
if chunk:
self.response += chunk
else:
urls_todo.remove(self.url)
if not urls_todo:
stopped = True
break
class Task:
"""
Task 实例对象的作用:
通过step()启动和恢复生成器的执行
"""
def __init__(self, coro):
print('task __init__')
self.coro = coro # 生成器
f = Future()
f.set_result(None)
print('task __init__ future=', f)
# 初始化第一次调用step,用于启动生成器fetch(),即这里的coro
# 因为第一次启动生成器发送的必须是None,所以这里的f的作用就是提供有None值的Future对象
self.step(f)
def step(self, future):
"""
step方法作用:恢复这个生成器的执行
"""
print('task step')
try:
print('future=', future)
print('future.result=', future.result)
# next_future第一次是f1,第二次是f2
# 如果服务器返回的数据太多,一次性接收不完,会通过fetch中的while True循环不断yield f2来接收
# 所以next_future会一直是f2直到数据接收完成
next_future = self.coro.send(future.result)
print('task nect_future=', next_future)
except StopIteration:
return
# 得到下一次的future,然后给下一次的future添加step()回调。
# 原来add_done_callback()不是给写爬虫业务逻辑用的
# 而是用于恢复生成器的执行
next_future.add_done_callback(self.step)
def loop():
while not stopped:
events = selector.select()
for event_key, event_musk in events:
callback = event_key.data
print('loop callback=', callback)
callback()
print('--------------------')
if __name__ == '__main__':
import time
start = time.time()
for url in urls_todo:
crawler = Crawler(url)
# crawler.fetch() 返回了一个生成器对象,并不是已经调用了fetch函数,只是预激活生成器
# 真正的调用是在Task的step函数中:self.coro.send(future.result)
# self.coro就是生成器对象crawler.fetch()
Task(crawler.fetch())
# 初始化Task对象以后,把fetch()给驱动到了fetch中的yied f1就完事了
# 接下来靠事件循环
print('-----------------')
# 接下来,只需等待已经注册的事件发生
# 事件循环就像心脏一般,只要它开始跳动,整个程序就会持续运行
loop()
print(f'程序运行时间:{time.time() - start}s')
4.5 用 yield from 改进生成器协程
yield from基本概念参考:yield from