import asyncio import threading import tornado.queues # As long as we support Python35, we use this library to get as async # generators: https://pypi.org/project/async_generator/ from async_generator import async_generator, yield_ class ThreadedAsyncGenerator(threading.Thread): def __init__(self, main_ioloop, fn, *args, **kwargs): super(ThreadedAsyncGenerator, self).__init__() self.main_ioloop = main_ioloop self.fn = fn self.args = args self.kwargs = kwargs self.queue = tornado.queues.Queue() self.start() def run(self): ioloop_in_thread = asyncio.new_event_loop() asyncio.set_event_loop(ioloop_in_thread) return ioloop_in_thread.run_until_complete(self._run()) async def _run(self): async for item in self.fn(*self.args, **self.kwargs): def thread_safe_put(item=item): self.queue.put(item) self.main_ioloop.call_soon_threadsafe(thread_safe_put) def thread_safe_end(): self.queue.put(StopIteration) self.main_ioloop.call_soon_threadsafe(thread_safe_end) @async_generator async def __aiter__(self): while True: value = await self.queue.get() if value == StopIteration: break await yield_(value) def async_generator_to_thread(fn): """Calls an async generator function fn in a thread and async returns the results""" ioloop = asyncio.get_event_loop() def wrapper(*args, **kwargs): gen = ThreadedAsyncGenerator(ioloop, fn, *args, **kwargs) return gen return wrapper