Last active
July 14, 2023 13:09
-
-
Save martinkozle/68479ea35d4fade4a3d4f83da87f98d6 to your computer and use it in GitHub Desktop.
Asynchronously iterate a synchronous IO blocking iterator using a thread pool
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import asyncio | |
from concurrent.futures import ThreadPoolExecutor | |
from functools import partial | |
from typing import AsyncIterator, Iterator, TypeVar | |
T = TypeVar("T") | |
def checked_next(iterator: Iterator[T]) -> T | None: | |
try: | |
return next(iterator) | |
except StopIteration: | |
return None | |
async def sync_to_async_iter(iterator: Iterator[T]) -> AsyncIterator[T]: | |
with ThreadPoolExecutor(max_workers=1) as executor: | |
while ( | |
value := await asyncio.get_running_loop().run_in_executor( | |
executor, partial(checked_next, iterator) | |
) | |
) is not None: | |
yield value |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
The
sync_to_async_iter
function executes thenext
function on a synchronous iterator in a separate thread asynchronously so that it doesn't block the main program.It is meant to be used on an iterator that does some long IO blocking operation on every next call.
In the
checked_next
functionNone
is used as a sentinel value to signal when the iteration is over. This is done becauseStopIteration
cannot be raised into aFuture
. This has the side effect of if your iterator actually yieldsNone
it will stop the asynchronous iteration early. If you need support for yielding None, something more robust like this can be used:Alternatively if you don't care about static type checking you can use
StopIterationSentinel = object()
.For more info about sentinel values check PEP 661.