Question Details

No question body available.

Tags

python multithreading python-asyncio event-loop

Answers (3)

Accepted Answer Available
Accepted Answer
May 29, 2025 Score: 2 Rep: 45,905 Quality: High Completeness: 80%
  1. We first create a daemon thread in which a new event loop runs.
  2. Function submit runs an arbitrary coroutine in the event loop/thread created in step 1. If argument returnresult is True (the default), submit waits for the coroutine to complete and returns its result. Otherwise, submit return a Future and the caller can get the coroutine's result by executing the future's result method. This function is used by the functions described next.
  3. Function createtask creates a task in the daemon thread and returns that task.
  4. The returned task can be awaited and the task's result return by calling awaittask with the returnresult argument set to True (the default). This will block the main thread until the result can be returned. Alternatively, awaittask can be called with returnresult=False, in which case a Future will be returned whose result can be obtained later.
import asyncio
import threading

eventloop = asyncio.neweventloop() threading.Thread(target=eventloop.runforever, name="Async Runner", daemon=True).start()

def submit(coro, return
result=True): """Run a coroutine in the "other thread". If returnresult is True, we return the result. Otherwise we return the future.""" future = asyncio.runcoroutinethreadsafe(coro, eventloop) return future.result() if returnresult else future

def createtask(coro): """Creates a task in the other thread and return the task.""" async def taskcreator(): return asyncio.createtask(coro)

return submit(task
creator())

def awaittask(task, returnresult=True): """Submit to the returnresult thread a coroutine to await the passed thread. If wait is True, we wait for the task to complete and return the result. Otherwise, we return to the caller a future whose result can be obtained when the caller wants.""" async def waiter(): return await task

return submit(waiter(), return
result=returnresult)

if name == 'main': async def some
coro(): await asyncio.sleep(1) return 'Done'

async def main(): task = createtask(somecoro())

# We choose to have a future returned instead # of the actual result from executing the task: future = awaittask(task, returnresult=False) ... # Do other work print(future.result())

asyncio.run(main())

Prints:

Done

Update

Here is another implementation where waiting for the submitted task to complete does not block the main thread by using asyncio.tothread. Now all functions are async:

import asyncio
import threading

eventloop = asyncio.neweventloop() threading.Thread(target=eventloop.runforever, name="Async Runner", daemon=True).start()

async def submit(coro): """Run a coroutine in the "other thread and await its result""" def submitter(): future = asyncio.runcoroutinethreadsafe(coro, eventloop) return future.result()

return await asyncio.tothread(submitter)

async def create
task(coro): """Creates a task in the other thread and return the task.""" async def taskcreator(): return asyncio.createtask(coro)

return await submit(taskcreator())

async def await
task(task): """Await the task running in the other thread and return its result.""" async def waiter(): return await task

return await submit(waiter())

if name == 'main': async def somecoro(): await asyncio.sleep(1) return 'Done'

async def main(): task = await create
task(somecoro()) ... # Do other work print(await awaittask(task))

asyncio.run(main())
May 29, 2025 Score: 2 Rep: 18,103 Quality: Medium Completeness: 60%

It can be done in two steps. The first call returns an auxilliary concurrent future (future1 in the program) that will quickly give you the task reference and the real future (future2) to wait for.

Note: Python 3.12+ is required or else you have to change the way the loop is passed between threads.

import asyncio
from threading import Thread

stop = asyncio.Event()

async def asyncmain(): await stop.wait()

def async
start(loop): asyncio.run(asyncmain(), loopfactory=lambda: loop)

async def asyncstop(): stop.set()

async def test
task(n): for i in range(n): print(i) await asyncio.sleep(0.2) return "test result"

async def newtask(coro): task = asyncio.createtask(coro) async def waiter(): return await task return ( task, asyncio.runcoroutinethreadsafe( waiter(), asyncio.getrunningloop()))

def main(): loop = asyncio.neweventloop() def submit(coro): return asyncio.runcoroutinethreadsafe(coro, loop)

thread = Thread(target=asyncstart, args=(loop,)) thread.start() future1 = submit(newtask(testtask(5))) task, future2 = future1.result() print(f"{task.getname()=}") result = future2.result() print(f"{result=}") submit(async_stop()) thread.join()

if name == "main": main()
May 29, 2025 Score: 1 Rep: 44 Quality: Low Completeness: 60%

You can wrap the coroutine to return a task. Thus the first future will return the task (note: this task is bound to the loop and thus cannot be run in a different loop). Then you can simply make use of another asyncio.runcoroutinethreadsafe to run the actual coroutine with the original loop and get the desired result.

A sample program to demonstrate the same:

import asyncio
import threading

def starteventloop(loop): asyncio.seteventloop(loop) loop.runforever()

async def simpleTask(): await asyncio.sleep(1) print("Task done") return True

async def createTask(): return asyncio.createtask(simpleTask(), name="BackgroundTask")

async def runTask(coro): return await coro

loop = asyncio.neweventloop() # the common event loop thread = threading.Thread(target=starteventloop, args=(loop,), daemon=True) thread.start()

future = asyncio.runcoroutinethreadsafe(createTask(), loop)

task = future.result()

print(f"Got Task Type: {type(task)}, Name: {task.getname()}")

future1 = asyncio.runcoroutinethreadsafe(runTask(task), loop)

task2 = future_1.result()

print(f"Got: {task2}")

Output:

Got Task Type: , Name: BackgroundTask Task done Got: True