This post is about python asyncio. asyncio is python asynchronous implementation providing event loop functionality. From “event loop” wiki:
In computer science, the event loop is a programming construct or design pattern that waits for and dispatches events or messages in a program. The event loop works by making a request to some internal or external “event provider” (that generally blocks the request until an event has arrived), then calls the relevant event handler (“dispatches the event”). The event loop is also sometimes referred to as the message dispatcher, message loop, message pump, or run loop.
asyncio defines awaitables
and one important awaitables is coroutine
which is a python task with keyword async
. coroutines are called with await
from other routines.
In [1]: import asyncio
In [2]: async def main():
...: ... print('hello')
...: ... await asyncio.sleep(1)
...: ... print('world')
...:
In [3]: main
Out[3]: <function __main__.main()>
In [4]: main()
Out[4]: <coroutine object main at 0x7f988a718040>
In [5]: asyncio.run(main())
hello
world
asyncio.run
starts the event loop and adds the coroutines. From the help:
run(main, *, debug=None)
Execute the coroutine and return the result.
This function runs the passed coroutine, taking care of
managing the asyncio event loop and finalizing asynchronous
generators.
This function cannot be called when another asyncio event loop is
running in the same thread.
At this point, I am not done yet. I thought to dip my toes into cpython. So, Jumping into cpython, run
is imported from cpython/Lib/asyncio/runners.py
188 with Runner(debug=debug) as runner:
189 return runner.run(main)
which calls run
from Runner
in the same file. The _loop
is init with self._loop = events.new_event_loop()
inside _lazy_init
. Then task
is created a and passed to run_until_complete
.
85 def run(self, coro, *, context=None):
...
...
99 task = self._loop.create_task(coro, context=context)
100
101 if (threading.current_thread() is threading.main_thread()
102 and signal.getsignal(signal.SIGINT) is signal.default_int_handler
103 ):
104 sigint_handler = functools.partial(self._on_sigint, main_task=task)
105 try:
106 signal.signal(signal.SIGINT, sigint_handler)
107 except ValueError:
108 # `signal.signal` may throw if `threading.main_thread` does
109 # not support signals (e.g. embedded interpreter with signals
110 # not registered - see gh-91880)
111 sigint_handler = None
112 else:
113 sigint_handler = None
114
115 self._interrupt_count = 0
116 try:
117 return self._loop.run_until_complete(task)
There is section above where it uses partial
to pass task
to self._on_sigint
when SIGINT happens.
create_task
is called to return task. Task
is defined in tasks.py
429 def create_task(self, coro, *, name=None, context=None):
430 """Schedule a coroutine object.
431
432 Return a task object.
433 """
434 self._check_closed()
435 if self._task_factory is None:
436 task = tasks.Task(coro, loop=self, name=name, context=context)
437 if task._source_traceback:
438 del task._source_traceback[-1]
439 else:
440 if context is None:
441 # Use legacy API if context is not needed
442 task = self._task_factory(self, coro)
443 else:
444 task = self._task_factory(self, coro, context=context)
445
446 tasks._set_task_name(task, name)
447
448 return task
my laptop battery is dying. So, I will have to do part 2 of whatever this is.