Search code examples
pythonasync-awaitpython-asynciocoroutineevent-loop

python asyncio `loop.create_task` add task in another thread's loop, the task randomly get stucked


console's output

the code:

import asyncio
import time
from threading import Thread
import threading


loop = asyncio.new_event_loop()

def print_ids(s, l):
    print(f"{threading.current_thread().name}: self: {id(s)}")
    print(f"{threading.current_thread().name}: loop: {id(l)}")


class CorotineLoop:
    def __init__(self):
        # start thread
        self.thread1 = Thread(
            target=self.run_async1,
            daemon=True,
            name="one"
        )
        self.thread1.start()
        print_ids(self, loop)
        return

    def run_async1(self):
        # start loop
        print_ids(self, loop)
        asyncio.set_event_loop(loop)
        loop.run_forever()

    async def _run(self):
        # print stuff
        for i in range(2):
            print(threading.current_thread().name + ":", i)
            await asyncio.sleep(1)
        loop.close()

    def submit(self):
        # submit a task
        print("submitting...")
        loop.create_task(self._run())
        print("submitted")
        return



cloop = CorotineLoop()
cloop.submit()

print("start wating..")
while 1:
    time.sleep(0.2)

what i'm doing is i want to create a thread simply for running async coroutines, the main thread will be running other normal sync tasks

but randomly, it stucks for no reason, sometimes ok somtimes not.

i may have it written in a wrong way, so if anyone can fix it for me?

i tried to use vscode to run debugging to find out why and where it's stucked, but it then works fine no matter how many times i've tried which leaves me no clue.


Solution

  • You can´t call "create_task" from anothe thread from that in which the loop is running.

    The way to do that is to use loop.call_soon_threadsafe() https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.call_soon_threadsafe -

    Your submit method can be modified like this, and if you want _run to be a co-routine, a wrapper synchronous function to be run in the same thread as the loop is running is needed to create a task with the co-routine

        def submit(self):
            # submit a task
            print("submitting...")
            def _in_thread_callback():
                loop.create_task(self._run())
            loop.call_soon_threadsafe(_in_thread_callback)
            print("submitted")
            return