Lightning talks
Run in another thread without callback
How block the loop?
Some code
CPU intensive
Sync operation
Run in another thread
Use async lib
IO
CPU
Any "aio-{name}" lib?
Run in another thread
async def some_func():
time.sleep(100)
Run in another thread
import asyncio
def callback():
time.sleep(100)
async def some_func():
await asyncio.get_runing_loop().run_in_executor(None, callback)
Run in another thread with args
import asyncio
import functools
def callback(t=100):
time.sleep(t)
async def some_func():
some_name = functools.partial(callback, t=10)
await asyncio.get_runing_loop().run_in_executor(None, some_name)
Threadpool decorator
import asyncio
def run_in_threadpool(func):
@functools.wraps(func)
def wrap(*args, **kwargs):
def inner():
return func(*args, **kwargs)
return asyncio.get_running_loop().run_in_executor(None, inner)
return wrap
@run_in_threadpool
def callback(t=100):
time.sleep(t)
async def some_func():
await callback(10)

@run_in_threadpool
def some_function(args):
some_values = some_code()
...
...
return a, b, c
async def handler(request):
await something(...)
...
a, b, c = await some_function(...)
some_other_code(b)
return a
Return values

Any other variants?
async def http_handler(request):
await some_func()
async with threadpool():
time.sleep(100)
a = 'look mom no def but run in another thread'
print(a)
return 'all ok'
Context manager?
pip install asyncio_extras
import asyncio
import threading
import time
from asyncio_extras import threadpool
async def som_handler():
main_thread_id = threading.get_ident()
async with threadpool():
time.sleep(1)
thread_id = threading.get_ident()
assert main_thread_id != thread_id
asyncio.run(som_handler())
How does it works?

class _ThreadSwitcher:
__slots__ = 'executor', 'exited'
def __init__(self, executor: Optional[Executor]) -> None:
self.executor = executor
self.exited = False
def __aenter__(self):
# This is run in the event loop thread
return self
...
...
def __await__(self):
def exec_when_ready():
event.wait()
coro.send(None)
if not self.exited:
raise RuntimeError('attempted to "await" in a worker thread')
if self.exited:
# This is run in the worker thread
yield
else:
# This is run in the event loop thread
previous_frame = inspect.currentframe().f_back
coro = next(obj for obj in gc.get_referrers(previous_frame.f_code)
if inspect.iscoroutine(obj) and obj.cr_frame is previous_frame)
event = Event()
loop = get_event_loop()
future = loop.run_in_executor(self.executor, exec_when_ready)
next(future.__await__()) # Make the future think it's being awaited on
loop.call_soon(event.set)
yield future
...
def __aexit__(self, exc_type, exc_val, exc_tb):
# This is run in the worker thread
self.exited = True
return self
...
...
def __call__(self, func: Callable) -> Callable:
@wraps(func)
def wrapper(*args, **kwargs):
try:
loop = get_event_loop()
except RuntimeError:
# Event loop not available -- we're in a worker thread
return func(*args, **kwargs)
else:
callback = partial(func, *args, **kwargs)
return loop.run_in_executor(self.executor, callback)
assert not inspect.iscoroutinefunction(func), \
'Cannot wrap coroutine functions to be run in an executor'
return wrapper
Performance?
No difference found

Limitations?
async def some_asyncio_handler():
await save_to_db()
await some_func()
return 'ok'
Sync code not affected
Also any flask app just ignore client disconnects
async with threadpool()
Run sync like async code, and we can catch CancellError in any place.
This is very dangerous
try:
val = some_code()
except Exception:
print('oh no')
some_fallback()
This is very dangerous
try:
val = some_code()
except CancellError:
raise
except Exception:
print('oh no')
some_fallback()
Use only if ...
You fully understood _ThreadSwitcher code
Шпасибо
PyConRu#19
By Denis Kataev
PyConRu#19
- 908