异步函数调用方式,除了call_soon(call_soon_threadsafe)还有一种延迟调用,即call_later,call_at.通过查看源码,会轻易的发现, call_later 其实是通过call_at实现的.
def call_later(self, delay, callback, *args):
timer = self.call_at(self.time() + delay, callback, *args)
if timer._source_traceback:
del timer._source_traceback[-1]
return timer
所以只需要研究 call_at 就可以了.
def call_at(self, when, callback, *args):
self._check_closed()
if self._debug:
self._check_thread()
self._check_callback(callback, 'call_at')
timer = events.TimerHandle(when, callback, args, self)
if timer._source_traceback:
del timer._source_traceback[-1]
heapq.heappush(self._scheduled, timer)
timer._scheduled = True
return timer
可以看出,在 call_at 内部实际是构建了一个和之前call_soon里看到的 Handle 类似的events.TimerHandle.
而这个events.TimerHandle 与之前的Handle 在于它重载(或许重写比较合适)了大小比较,即Handle 之间可以比较,比较的依据就是它理应(注意此处的理应)执行的时间的时间戳.所以之后的:
heapq.heappush(self._scheduled, timer)
就有了比较的依据,由于python中的优先队列是 min heap .即大小顺序是又小到大,所以时间戳小的在前,及理应早些发生的在前面. 虽然这里构造了TimerHandle 实例,但是并没有真正的执行, 真正的执行还是在call_soon 里提到的_run_once中.
sched_count = len(self._scheduled)
if (sched_count > _MIN_SCHEDULED_TIMER_HANDLES and
self._timer_cancelled_count / sched_count >
_MIN_CANCELLED_TIMER_HANDLES_FRACTION):
# Remove delayed calls that were cancelled if their number
# is too high
new_scheduled = []
for handle in self._scheduled:
if handle._cancelled:
handle._scheduled = False
else:
new_scheduled.append(handle)
heapq.heapify(new_scheduled)
self._scheduled = new_scheduled
self._timer_cancelled_count = 0
else:
# Remove delayed calls that were cancelled from head of queue.
while self._scheduled and self._scheduled[0]._cancelled:
self._timer_cancelled_count -= 1
handle = heapq.heappop(self._scheduled)
handle._scheduled = False
先是将已经取消的任务去掉.然后根据最先要执行的任务的时间,决定等待多长时间.
timeout = None
if self._ready or self._stopping:
timeout = 0
elif self._scheduled:
# Compute the desired timeout.
when = self._scheduled[0]._when
timeout = max(0, when - self.time())
event_list = self._selector.select(timeout)
self._process_events(event_list)
然后计算并比较当前时间和任务的理论执行时间,决定哪些任务将要执行
# Handle 'later' callbacks that are ready.
end_time = self.time() + self._clock_resolution
while self._scheduled:
handle = self._scheduled[0]
if handle._when >= end_time:
break
handle = heapq.heappop(self._scheduled)
handle._scheduled = False
self._ready.append(handle)
可以看到和call_soon一样,都是添加到self._ready这个队列中,依照FIFO原则执行.
在结尾处我们探讨一下为什么之前说 理应执行时间 ?
其实由最终call_at添加的计划任务转到self._ready这个队列中就可以简单的推测出,如果在延迟的任务前,有一个特别耗时的操作,后面的任务即便到了理论时间还是需要等待当前任务完成的.下面给出一个不太科学的例子,可以自己体会一下:
#!/usr/bin/python3
#-*-coding:utf8-*-
'''
学习asyncio 之 call_later,
观察 call_later 到底什么时候执行的.
'''
import time
import asyncio
import requests
def block_loop():
print("In block")
print(time.time())
try:
requests.get("https://www.google.com")#一个延迟高甚至不可达的地址即可
except:
pass
def hello_world(loop):
print(time.time())
print('Hello World')
loop.stop()
loop = asyncio.get_event_loop()
loop.set_debug(True)
print(time.time())
# Schedule a call to hello_world()
loop.call_soon(block_loop)
print("call soon")
print(time.time())
loop.call_later(2, hello_world, loop)
print("Register call later.")
print(time.time())
# Blocking call interrupted by loop.stop()
print("Start loop")
loop.run_forever()
loop.close()
本文深入解析了Python的AsyncIO模块中call_at与call_later的实现原理,介绍了它们如何通过构建TimerHandle来调度定时任务,并探讨了这些任务的实际执行流程。

621

被折叠的 条评论
为什么被折叠?



