Python 3-asyncio Learning Notes 2-call_at

Keywords: Python less Google

Asynchronous function invocation, in addition to call_soon(call_soon_threadsafe), there is also a delay call, that is, call_later,call_at. By looking at the source code, you can easily find that call_late is actually achieved through 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

So you just need to study 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

As you can see, inside call_at, you actually build an event. TimerHandle similar to the Handle you saw in call_soon.

And this event. TimerHandle is compared to the previous Handle in terms of its overload (perhaps rewriting is more appropriate), that is, the comparison between Handles is based on the timestamp of the time it should execute (note what should be done here). So after that:

heapq.heappush(self._scheduled, timer)

There is a basis for comparison, because the priority queue in python is min heap. That is, the order of size is small to large, so the time stamp is small before, and should have occurred earlier. Although TimerHandle instance is constructed here, but there is no real execution, the real execution is still in _run_once mentioned in call_soon.

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

First remove the cancelled tasks. Then decide how long to wait based on the time of the first task to be performed.

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)

Then calculate and compare the current time and theoretical execution time of tasks, and decide which tasks will be executed.

# 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)

As you can see, like call_soon, they are added to the self. _read queue and executed according to FIFO principles.

At the end, let's discuss why the execution time should have been said before.

In fact, it can be simply inferred that if there is a particularly time-consuming operation before the delayed task, the latter task will have to wait for the current task to complete even when the theoretical time is up. Here is a less scientific example that you can experience for yourself:

#!/usr/bin/python3
#-*-coding:utf8-*-
'''
//Learn asyncio call_later. 
//Observe when call_later actually executes.
'''
import time
import asyncio
import requests


def block_loop():
    print("In block")
    print(time.time())
    try:
        requests.get("https://www.google.com"6550
    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()

Posted by spivey on Thu, 27 Dec 2018 09:27:06 -0800