Muestra las diferencias entre dos versiones de la página.
| Ambos lados, revisión anterior Revisión previa Próxima revisión | Revisión previa | ||
|
wiki2:python3 [2020/06/20 12:50] alfred [The event loop] |
wiki2:python3 [2022/10/12 19:03] (actual) |
||
|---|---|---|---|
| Línea 88: | Línea 88: | ||
| Co-routines are functions that in a way similar to green threads are executed in a single thread and process. However, they are not so costly as are not managed by the operating system but by the **event loop**. | Co-routines are functions that in a way similar to green threads are executed in a single thread and process. However, they are not so costly as are not managed by the operating system but by the **event loop**. | ||
| + | For an advance use go to [[wiki2:python:notes#advanced_asyncio|Advanced asyncio]]. | ||
| ==== Basic points ==== | ==== Basic points ==== | ||
| Línea 260: | Línea 261: | ||
| ''create_task'' and ''create_future'' . There is also the ''set_task_factory'' if you want to create your own task instances. | ''create_task'' and ''create_future'' . There is also the ''set_task_factory'' if you want to create your own task instances. | ||
| - | === Futures and executors === | + | ==== Futures and executors ==== |
| + | A ''Future'' represents an eventual result of an asynchronous operation. | ||
| + | <code python> | ||
| + | async def set_after(fut, delay, value): | ||
| + | await asyncio.sleep(delay) | ||
| + | fut.set_result(value) | ||
| + | async def main(): | ||
| + | loop = asyncio.get_running_loop() | ||
| + | fut = loop.create_future() | ||
| + | loop.create_task(set_after(fut, 1, '... world')) # We could have just used "asyncio.create_task()". | ||
| + | print('hello ...') | ||
| + | print(await fut) # Wait until *fut* has a result (1 second) and print it. | ||
| + | |||
| + | asyncio.run(main()) | ||
| + | </code> | ||
| + | |||
| + | An instance of ''Executor'' provides methods to execute calls asynchronously. It should not be used directly, but through its concrete subclasses. | ||
| + | |||
| + | <code python> | ||
| + | async def main(): | ||
| + | loop = asyncio.get_running_loop() | ||
| + | result = await loop.run_in_executor( # 1. Returns a Future.Executor | ||
| + | None, blocking_io) | ||
| + | print('default thread pool', result) | ||
| + | with concurrent.futures.ThreadPoolExecutor() as pool: # 2. Run in a custom thread pool: | ||
| + | result = await loop.run_in_executor( | ||
| + | pool, blocking_io) | ||
| + | print('custom thread pool', result) | ||
| + | with concurrent.futures.ProcessPoolExecutor() as pool: # 3. Run in a custom process pool: | ||
| + | result = await loop.run_in_executor( | ||
| + | pool, cpu_bound) | ||
| + | print('custom process pool', result) | ||
| + | </code> | ||
| + | |||
| + | ''ThreadPoolExecutor'' uses a pool of threads in the same way than ''ProcessPoolExecutor'' uses a pool of processes. | ||
| + | <code python> | ||
| + | executor = ThreadPoolExecutor(max_workers=2) | ||
| + | a = executor.submit(wait_on_b) | ||
| + | b = executor.submit(wait_on_a) | ||
| + | </code> | ||
| + | |||
| + | ==== Other tools ==== | ||
| + | |||
| + | === Streams === | ||
| + | * [[https://docs.python.org/3/library/asyncio-stream.html]] | ||
| + | These allow to manage IO resources (sockets) asynchronously. | ||
| + | |||
| + | === Synchronization === | ||
| + | * [[https://docs.python.org/3/library/asyncio-sync.html]] | ||
| + | |||
| + | You can make use of the usual tools for synchronize co-routines (Lock, Event, Condition, Semaphore, BoundedSemaphore). | ||
| + | |||
| + | === Queues === | ||
| + | * [[https://docs.python.org/3/library/asyncio-queue.html]] | ||
| + | |||
| + | Asynchronous queues that can be used to distribute workload between several concurrent tasks: | ||
| + | <code python> | ||
| + | import asyncio | ||
| + | import random | ||
| + | import time | ||
| + | |||
| + | async def worker(name, queue): | ||
| + | while True: | ||
| + | sleep_for = await queue.get() | ||
| + | await asyncio.sleep(sleep_for) | ||
| + | queue.task_done() | ||
| + | |||
| + | async def main(): | ||
| + | queue = asyncio.Queue() | ||
| + | total_sleep_time = 0 | ||
| + | for _ in range(20): | ||
| + | sleep_for = random.uniform(0.05, 1.0) | ||
| + | total_sleep_time += sleep_for | ||
| + | queue.put_nowait(sleep_for) | ||
| + | |||
| + | # Create three worker tasks to process the queue concurrently. | ||
| + | tasks = [] | ||
| + | for i in range(3): | ||
| + | task = asyncio.create_task(worker(f'worker-{i}', queue)) | ||
| + | tasks.append(task) | ||
| + | # Wait until the queue is fully processed. | ||
| + | started_at = time.monotonic() | ||
| + | await queue.join() | ||
| + | total_slept_for = time.monotonic() - started_at | ||
| + | # Cancel our worker tasks. | ||
| + | for task in tasks: | ||
| + | task.cancel() | ||
| + | # Wait until all worker tasks are cancelled. | ||
| + | await asyncio.gather(*tasks, return_exceptions=True) | ||
| + | |||
| + | print('====') | ||
| + | print(f'3 workers slept in parallel for {total_slept_for:.2f} seconds') | ||
| + | print(f'total expected sleep time: {total_sleep_time:.2f} seconds') | ||
| + | asyncio.run(main()) | ||
| + | </code> | ||
| ==== Notes on co-routines ==== | ==== Notes on co-routines ==== | ||
| Línea 298: | Línea 393: | ||
| * [[https://news.ycombinator.com/item?id=23498742|Discussion on Hacker News]] | * [[https://news.ycombinator.com/item?id=23498742|Discussion on Hacker News]] | ||
| + | |||
| + | === functools.partial for passing arguments === | ||
| + | ''functools.partial'' allows to create a function that not requires arguments from one that requires them. | ||
| + | <code python> | ||
| + | loop.call_soon(partial(print, "Hello", flush=True)) | ||
| + | </code> | ||
| + | |||
| + | === Stop co-routines when signals === | ||
| + | <code python> | ||
| + | import asyncio | ||
| + | import functools | ||
| + | import os | ||
| + | import signal | ||
| + | |||
| + | def ask_exit(signame, loop): | ||
| + | print("got signal %s: exit" % signame) | ||
| + | loop.stop() | ||
| + | |||
| + | async def main(): | ||
| + | loop = asyncio.get_running_loop() | ||
| + | |||
| + | for signame in {'SIGINT', 'SIGTERM'}: | ||
| + | loop.add_signal_handler( | ||
| + | getattr(signal, signame), | ||
| + | functools.partial(ask_exit, signame, loop)) | ||
| + | |||
| + | await asyncio.sleep(3600) | ||
| + | |||
| + | print("Event loop running for 1 hour, press Ctrl+C to interrupt.") | ||
| + | print(f"pid {os.getpid()}: send SIGINT or SIGTERM to exit.") | ||
| + | |||
| + | asyncio.run(main()) | ||
| + | </code> | ||
| + | |||
| + | ===== Enums ===== | ||
| + | |||
| + | <code python> | ||
| + | from enum import Enum | ||
| + | |||
| + | class Numbers(Enum): | ||
| + | ONE = 1 | ||
| + | TWO = 2 | ||
| + | |||
| + | number = Numbers(2) # <Numbers.TWO: 2> | ||
| + | number1 = Numbers["ONE"] # <Numbers.TWO: 2> | ||
| + | members = [n for n in Numbers] # [<Numbers.ONE: 1>, <Numbers.TWO: 2>] | ||
| + | values = [n.value for n in Numbers] # [1, 2] | ||
| + | </code> | ||
| + | |||
| + | ==== Links ==== | ||
| + | |||
| + | * https://realpython.com/python-enum/ | ||
| + | |||
| + | ===== Python type checking ===== | ||
| + | |||
| + | <code> | ||
| + | pi: float = 3.142 | ||
| + | |||
| + | def headline(text: str, align: bool = True) -> str: | ||
| + | pass | ||
| + | | ||
| + | | ||
| + | </code> | ||
| + | |||
| + | The ''MyPy'' library allows to check code files. | ||
| + | |||
| + | <code> | ||
| + | >>> names: list = ["Guido", "Jukka", "Ivan"] | ||
| + | >>> version: tuple = (3, 7, 1) | ||
| + | >>> options: dict = {"centered": False, "capitalize": True} | ||
| + | >>> from typing import Dict, List, Tuple | ||
| + | >>> names: List[str] = ["Guido", "Jukka", "Ivan"] | ||
| + | >>> version: Tuple[int, int, int] = (3, 7, 1) | ||
| + | >>> options: Dict[str, bool] = {"centered": False, "capitalize": True} | ||
| + | </code> | ||
| + | |||
| + | <code> | ||
| + | import random | ||
| + | from typing import Any, Sequence | ||
| + | |||
| + | def choose(items: Sequence[Any]) -> Any: | ||
| + | return random.choice(items) | ||
| + | </code> | ||
| + | |||
| + | <code> | ||
| + | class Animal: | ||
| + | def __init__(self, name: str, birthday: date) -> None: | ||
| + | self.name = name | ||
| + | self.birthday = birthday | ||
| + | |||
| + | @classmethod | ||
| + | def newborn(cls, name: str) -> "Animal": | ||
| + | return cls(name, date.today()) | ||
| + | |||
| + | def twin(self, name: str) -> "Animal": | ||
| + | cls = self.__class__ | ||
| + | return cls(name, self.birthday) | ||
| + | </code> | ||
| + | |||
| + | <code> | ||
| + | def headline(text, width=80, fill_char="-"): | ||
| + | # type: (str, int, str) -> str | ||
| + | return f" {text.title()} ".center(width, fill_char) | ||
| + | |||
| + | def headline( | ||
| + | text, # type: str | ||
| + | width=80, # type: int | ||
| + | fill_char="-", # type: str | ||
| + | ): # type: (...) -> str | ||
| + | return f" {text.title()} ".center(width, fill_char) | ||
| + | </code> | ||
| ===== Notes ===== | ===== Notes ===== | ||