官术网_书友最值得收藏!

Event loop

The event loop is the entity of AsyncIO and is in charge of scheduling all asynchronous actions that must executes concurrently. Fundamentally, it is just an infinite loop that waits for some events to happen and execute handlers associated with these events. In the case of AsyncIO, these handlers are coroutines.

An event loop is automatically created by asyncio when a process is started. A reference to this event loop can be retrieved with the get_event_loop function of the asyncio module. Event loops inherit from the BaseEventLoop abstract class.

This class contains several methods used to execute asynchronous code. One of them is the run_until_complete method. With these two methods, the wait coroutine of the previous part can now be executed:

loop = asyncio.get_event_loop()
loop.run_until_complete(wait(2))
loop.close()

This code is available in the event_loop.py script. When executed, it shows the following output:

wait for 2 seconds at 23:8:22
waited for 2 seconds at 23:8:24

The second print statement confirms that the execution lasted for 2 seconds, which is the expected behavior. The important point in this program is the fact that during these 2 seconds, other actions could run; the process was not blocked while sleeping. This is easy to test with only one change to this program. Instead of executing the wait coroutine once, let's run it twice at the same time:

loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.gather(
wait(2),
wait(1)))
loop.close()

The code is almost the same, but instead of providing a coroutine to the run_until_complete method, the result of the gather method is provided. The gather method returns a future that aggregates the result of the futures or coroutines being passed as arguments.

The coroutines are not used directly by the run_until_complete method. Several steps are needed before the coroutine can execute some code. This was already the case in the previous example. Let's now see what happens when run_until_complete is called. First, a task is created and the coroutine is associated with it. The task is an entity that tracks the current coroutine being executed and resumes it when needed. The future is created and added to the event loop.

A future allows the event loop to know when an action has completed, but it also allows it to cancel an ongoing action. The following figure shows all these steps:

Figure 2.7: Execution of two concurrent coroutines in asyncio

The two first steps consist of creating the coroutine objects. Then the gather function creates one task per coroutine that must be executed and then wraps them in a single future. This future will complete once both tasks have completed. The future is returned to the application, and the application provides this future as a parameter to the run_until_complete method. From that point, the code of the coroutine starts being executed. First, the wait(2) coroutine calls an asynchronous sleep. This suspends the execution of the coroutine and its tasks. This allows the event loop to start executing the wait(1) coroutine, which also calls the asynchronous sleep, and so is suspended.

At that point, the event loop is suspended because there is no active task to execute. Should another task be active at that time, it could execute some code. About 1 second after that, the first sleep timer fires. This wakes up the wait(1) coroutine, which can complete. At that point, the event loop is suspended again. After another second, the second sleep timer fires. This wakes up the wait(2) coroutine that can complete its execution. Since the two tasks have now completed, the future also completes. This triggers the stopping of the event loop.

Take some time to fully understand this sequence diagram. These are all the steps that must be perfectly understood to write asyncio code.

It is also important to note two things in this code. First, even thought the wait(2) coroutine starts its execution first, the wait(1) coroutine completes before it. This happens in a single-process, single-threaded program. Such interleaving is not possible with blocking sleep calls. It would require two distinct threads.

Second, during all that time, the call to run_until_complete blocks its caller. This call is usually the end of an asyncio program, with only some cleanup after that. When the run_until_complete function returns, the event loop is stopped. It can then be closed to free up its internal resources. It is possible to restart an event loop that is stopped, but it is not possible to restart an event loop that is closed. So, this sequence is possible:

loop.run_until_complete(wait(2))
loop.run_until_complete(wait(1))

This one raises an error:

loop.run_until_complete(wait(2))
loop.close()
loop.run_until_complete(wait(1))

So now that the execution of this program is clear, you should be able to guess what it prints on the console:

wait for 1 seconds at 23:33:31
wait for 2 seconds at 23:33:31
waited for 1 seconds at 23:33:32
waited for 2 seconds at 23:33:33

Surprise! The wait(1) coroutine effectively lasted for 1 second and the wait(2) coroutine for two seconds. However, the wait(1) coroutine started its execution before the wait(2) coroutine, even though they were provided the other way to the gather call. This is normal behavior. The order of execution of the coroutines provided to gather is not guaranteed. This should not be an issue for any asynchronous code, but keep it in mind.

主站蜘蛛池模板: 新野县| 浙江省| 遂川县| 宜章县| 信宜市| 华蓥市| 蕲春县| 黄龙县| 枣阳市| 都兰县| 长兴县| 澄江县| 南木林县| 周口市| 汕尾市| 临武县| 稻城县| 宁国市| 四会市| 金山区| 汝州市| 怀化市| 丽水市| 衡阳县| 自治县| 西宁市| 宁安市| 威信县| 深泽县| 涟水县| 宾阳县| 左云县| 新龙县| 秭归县| 凭祥市| 大荔县| 万盛区| 克什克腾旗| 南投县| 夹江县| 丘北县|