Why can asyncio event loop sometimes finish a task even when encountering `RuntimeError`?











up vote
1
down vote

favorite












I've been playing around with Python's asyncio. I think I have a reasonable understanding by now. But the following behavior puzzles me.



test.py:



from threading import Thread
import asyncio

async def wait(t):
await asyncio.sleep(t)
print(f'waited {t} sec')

def run(loop):
loop.run_until_complete(wait(2))

loop = asyncio.get_event_loop()
t = Thread(target=run, args=(loop,))
t.start()
loop.run_until_complete(wait(1))
t.join()


This code is wrong. I know that. The event loop can't be run while it's running, and it's generally not thread safe.



My question: why can wait(1) sometimes still finish its job?



Here's the output from two consecutive runs:



>>> py test.py
... Traceback (most recent call last):
... File "test.py", line 14, in <module>
... loop.run_until_complete(wait(1))
... File "C:PythonPython37libasynciobase_events.py", line 555, in run_until_complete
... self.run_forever()
... File "C:PythonPython37libasynciobase_events.py", line 510, in run_forever
...
... raise RuntimeError('This event loop is already running')
... RuntimeError: This event loop is already running
... waited 2 sec

>>> py test.py
... Traceback (most recent call last):
... File "test.py", line 14, in <module>
... loop.run_until_complete(wait(1))
... File "C:PythonPython37libasynciobase_events.py", line 555, in run_until_c
... omplete
... self.run_forever()
... File "C:PythonPython37libasynciobase_events.py", line 510, in run_forever
...
... raise RuntimeError('This event loop is already running')
... RuntimeError: This event loop is already running
... waited 1 sec
... waited 2 sec


The first run's behavior is what I expected - the main thread fails, but the event loop still runs wait(2) to finish in the thread t.



The second run is puzzling, how can wait(1) do its job when the RuntimeError is already thrown? I guess it has to do with thread synchronization and the non-thread-safe nature of the event loop. But I don't know exactly how this works.










share|improve this question


















  • 1




    Because threading is unsafe but sometimes you get lucky.
    – Martijn Pieters
    Nov 7 at 16:46















up vote
1
down vote

favorite












I've been playing around with Python's asyncio. I think I have a reasonable understanding by now. But the following behavior puzzles me.



test.py:



from threading import Thread
import asyncio

async def wait(t):
await asyncio.sleep(t)
print(f'waited {t} sec')

def run(loop):
loop.run_until_complete(wait(2))

loop = asyncio.get_event_loop()
t = Thread(target=run, args=(loop,))
t.start()
loop.run_until_complete(wait(1))
t.join()


This code is wrong. I know that. The event loop can't be run while it's running, and it's generally not thread safe.



My question: why can wait(1) sometimes still finish its job?



Here's the output from two consecutive runs:



>>> py test.py
... Traceback (most recent call last):
... File "test.py", line 14, in <module>
... loop.run_until_complete(wait(1))
... File "C:PythonPython37libasynciobase_events.py", line 555, in run_until_complete
... self.run_forever()
... File "C:PythonPython37libasynciobase_events.py", line 510, in run_forever
...
... raise RuntimeError('This event loop is already running')
... RuntimeError: This event loop is already running
... waited 2 sec

>>> py test.py
... Traceback (most recent call last):
... File "test.py", line 14, in <module>
... loop.run_until_complete(wait(1))
... File "C:PythonPython37libasynciobase_events.py", line 555, in run_until_c
... omplete
... self.run_forever()
... File "C:PythonPython37libasynciobase_events.py", line 510, in run_forever
...
... raise RuntimeError('This event loop is already running')
... RuntimeError: This event loop is already running
... waited 1 sec
... waited 2 sec


The first run's behavior is what I expected - the main thread fails, but the event loop still runs wait(2) to finish in the thread t.



The second run is puzzling, how can wait(1) do its job when the RuntimeError is already thrown? I guess it has to do with thread synchronization and the non-thread-safe nature of the event loop. But I don't know exactly how this works.










share|improve this question


















  • 1




    Because threading is unsafe but sometimes you get lucky.
    – Martijn Pieters
    Nov 7 at 16:46













up vote
1
down vote

favorite









up vote
1
down vote

favorite











I've been playing around with Python's asyncio. I think I have a reasonable understanding by now. But the following behavior puzzles me.



test.py:



from threading import Thread
import asyncio

async def wait(t):
await asyncio.sleep(t)
print(f'waited {t} sec')

def run(loop):
loop.run_until_complete(wait(2))

loop = asyncio.get_event_loop()
t = Thread(target=run, args=(loop,))
t.start()
loop.run_until_complete(wait(1))
t.join()


This code is wrong. I know that. The event loop can't be run while it's running, and it's generally not thread safe.



My question: why can wait(1) sometimes still finish its job?



Here's the output from two consecutive runs:



>>> py test.py
... Traceback (most recent call last):
... File "test.py", line 14, in <module>
... loop.run_until_complete(wait(1))
... File "C:PythonPython37libasynciobase_events.py", line 555, in run_until_complete
... self.run_forever()
... File "C:PythonPython37libasynciobase_events.py", line 510, in run_forever
...
... raise RuntimeError('This event loop is already running')
... RuntimeError: This event loop is already running
... waited 2 sec

>>> py test.py
... Traceback (most recent call last):
... File "test.py", line 14, in <module>
... loop.run_until_complete(wait(1))
... File "C:PythonPython37libasynciobase_events.py", line 555, in run_until_c
... omplete
... self.run_forever()
... File "C:PythonPython37libasynciobase_events.py", line 510, in run_forever
...
... raise RuntimeError('This event loop is already running')
... RuntimeError: This event loop is already running
... waited 1 sec
... waited 2 sec


The first run's behavior is what I expected - the main thread fails, but the event loop still runs wait(2) to finish in the thread t.



The second run is puzzling, how can wait(1) do its job when the RuntimeError is already thrown? I guess it has to do with thread synchronization and the non-thread-safe nature of the event loop. But I don't know exactly how this works.










share|improve this question













I've been playing around with Python's asyncio. I think I have a reasonable understanding by now. But the following behavior puzzles me.



test.py:



from threading import Thread
import asyncio

async def wait(t):
await asyncio.sleep(t)
print(f'waited {t} sec')

def run(loop):
loop.run_until_complete(wait(2))

loop = asyncio.get_event_loop()
t = Thread(target=run, args=(loop,))
t.start()
loop.run_until_complete(wait(1))
t.join()


This code is wrong. I know that. The event loop can't be run while it's running, and it's generally not thread safe.



My question: why can wait(1) sometimes still finish its job?



Here's the output from two consecutive runs:



>>> py test.py
... Traceback (most recent call last):
... File "test.py", line 14, in <module>
... loop.run_until_complete(wait(1))
... File "C:PythonPython37libasynciobase_events.py", line 555, in run_until_complete
... self.run_forever()
... File "C:PythonPython37libasynciobase_events.py", line 510, in run_forever
...
... raise RuntimeError('This event loop is already running')
... RuntimeError: This event loop is already running
... waited 2 sec

>>> py test.py
... Traceback (most recent call last):
... File "test.py", line 14, in <module>
... loop.run_until_complete(wait(1))
... File "C:PythonPython37libasynciobase_events.py", line 555, in run_until_c
... omplete
... self.run_forever()
... File "C:PythonPython37libasynciobase_events.py", line 510, in run_forever
...
... raise RuntimeError('This event loop is already running')
... RuntimeError: This event loop is already running
... waited 1 sec
... waited 2 sec


The first run's behavior is what I expected - the main thread fails, but the event loop still runs wait(2) to finish in the thread t.



The second run is puzzling, how can wait(1) do its job when the RuntimeError is already thrown? I guess it has to do with thread synchronization and the non-thread-safe nature of the event loop. But I don't know exactly how this works.







python python-asyncio






share|improve this question













share|improve this question











share|improve this question




share|improve this question










asked Nov 7 at 16:45









Chenfeng

17118




17118








  • 1




    Because threading is unsafe but sometimes you get lucky.
    – Martijn Pieters
    Nov 7 at 16:46














  • 1




    Because threading is unsafe but sometimes you get lucky.
    – Martijn Pieters
    Nov 7 at 16:46








1




1




Because threading is unsafe but sometimes you get lucky.
– Martijn Pieters
Nov 7 at 16:46




Because threading is unsafe but sometimes you get lucky.
– Martijn Pieters
Nov 7 at 16:46












2 Answers
2






active

oldest

votes

















up vote
1
down vote













Ohhh... never mind. I read the code of asyncio and figured it out. It's actually quite simple.



run_until_complete calls ensure_future(future, loop=self) before it checks self.is_running() (which is done in run_forever). Since the loop is already running, it can pick up the task before the RuntimeError is thrown. Of course it doesn't always happen because of the race condition.






share|improve this answer






























    up vote
    1
    down vote













    Exceptions are thrown per thread. The runtime error is raised in a different thread from the event loop. The event loop continues to execute, regardless.



    And wait(1) can sometimes finish it's job because you can get lucky. The asyncio loop internal data structures are not guarded against race conditions caused by using threads (which is why there are specific thread-support methods you should use instead). But the nature of race conditions is such that it depends on the exact order of events and that order can change each time you run your program, depending on what else your OS is doing at the time.



    The run_until_complete() method first calls asyncio.ensure_task() to add the coroutine to the task queue with a 'done' callback attached that will stop the event loop again, then calls loop.run_forever(). When the coroutine returns, the callback stops the loop. The loop.run_forever() call throws the RuntimeError here.



    When you do this from a thread, the task gets added to a deque object attached to the loop, and if that happens at the right moment (e.g. when the running loop is not busy emptying the queue), the running loop in the main thread will find it, and execute it, even if the loop.run_forever() call raised an exception.



    All this relies on implementation details. Different versions of Python will probably exhibit different behaviour here, and if you install an alternative loop (e.g. uvloop), there will almost certainly be different behaviour again.



    If you want to schedule coroutines from a different thread, use asyncio.run_coroutine_threadsafe(); it would :



    from threading import Thread
    import asyncio

    async def wait(t):
    print(f'going to wait {t} seconds')
    await asyncio.sleep(t)
    print(f'waited {t} sec')

    def run(loop):
    asyncio.run_coroutine_threadsafe(wait(2), loop)

    loop = asyncio.get_event_loop()
    t = Thread(target=run, args=(loop,))
    t.start()
    loop.run_until_complete(wait(1))
    t.join()


    The above doesn't actually complete the wait(2) coroutine because the wait(1) coroutine is being run with loop.run_until_complete() so its callback stops the loop again before the 2 second wait is over. But the coroutine is actually started:



    going to wait 1 seconds
    going to wait 2 seconds
    waited 1 sec


    but if you made the main-thread coroutine take longer (with, say, wait(3)) then the one scheduled from the thread would also complete. You'd have to do additional work to ensure that there are no more pending tasks scheduled to run with the loop before you shut it down.






    share|improve this answer























    • That was my vague guess as well, but I'm asking for a more specific answer. I've actually figured it out. Please see my answer.
      – Chenfeng
      Nov 7 at 16:55










    • @Chenfeng: that's exactly what my answer is saying :-)
      – Martijn Pieters
      Nov 7 at 16:59










    • Oh well... I didn't see your edits before I posted mine. Thanks anyway.
      – Chenfeng
      Nov 7 at 17:00






    • 1




      On your additional info: I find it better to call run_forever in a separate thread, and schedule tasks onto the loop from other threads. Once all tasks are completed as indicated by the concurrent.futures.Future returned by run_coroutine_threadsafe, use loop.call_soon_threadsafe(loop.stop) to stop the loop, and use thread.join() to make sure the loop has actually stopped. This ensures that all tasks are done, and all events happen in proper order. No guessing game.
      – Chenfeng
      Nov 7 at 18:15












    • @Chenfeng: closely related: How can you wait for completion of a callback submitted from another thread?. thread.join() is probably fine if all the thread does is run the loop.
      – Martijn Pieters
      Nov 7 at 18:17













    Your Answer






    StackExchange.ifUsing("editor", function () {
    StackExchange.using("externalEditor", function () {
    StackExchange.using("snippets", function () {
    StackExchange.snippets.init();
    });
    });
    }, "code-snippets");

    StackExchange.ready(function() {
    var channelOptions = {
    tags: "".split(" "),
    id: "1"
    };
    initTagRenderer("".split(" "), "".split(" "), channelOptions);

    StackExchange.using("externalEditor", function() {
    // Have to fire editor after snippets, if snippets enabled
    if (StackExchange.settings.snippets.snippetsEnabled) {
    StackExchange.using("snippets", function() {
    createEditor();
    });
    }
    else {
    createEditor();
    }
    });

    function createEditor() {
    StackExchange.prepareEditor({
    heartbeatType: 'answer',
    convertImagesToLinks: true,
    noModals: true,
    showLowRepImageUploadWarning: true,
    reputationToPostImages: 10,
    bindNavPrevention: true,
    postfix: "",
    imageUploader: {
    brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
    contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
    allowUrls: true
    },
    onDemand: true,
    discardSelector: ".discard-answer"
    ,immediatelyShowMarkdownHelp:true
    });


    }
    });














     

    draft saved


    draft discarded


















    StackExchange.ready(
    function () {
    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53194040%2fwhy-can-asyncio-event-loop-sometimes-finish-a-task-even-when-encountering-runti%23new-answer', 'question_page');
    }
    );

    Post as a guest















    Required, but never shown

























    2 Answers
    2






    active

    oldest

    votes








    2 Answers
    2






    active

    oldest

    votes









    active

    oldest

    votes






    active

    oldest

    votes








    up vote
    1
    down vote













    Ohhh... never mind. I read the code of asyncio and figured it out. It's actually quite simple.



    run_until_complete calls ensure_future(future, loop=self) before it checks self.is_running() (which is done in run_forever). Since the loop is already running, it can pick up the task before the RuntimeError is thrown. Of course it doesn't always happen because of the race condition.






    share|improve this answer



























      up vote
      1
      down vote













      Ohhh... never mind. I read the code of asyncio and figured it out. It's actually quite simple.



      run_until_complete calls ensure_future(future, loop=self) before it checks self.is_running() (which is done in run_forever). Since the loop is already running, it can pick up the task before the RuntimeError is thrown. Of course it doesn't always happen because of the race condition.






      share|improve this answer

























        up vote
        1
        down vote










        up vote
        1
        down vote









        Ohhh... never mind. I read the code of asyncio and figured it out. It's actually quite simple.



        run_until_complete calls ensure_future(future, loop=self) before it checks self.is_running() (which is done in run_forever). Since the loop is already running, it can pick up the task before the RuntimeError is thrown. Of course it doesn't always happen because of the race condition.






        share|improve this answer














        Ohhh... never mind. I read the code of asyncio and figured it out. It's actually quite simple.



        run_until_complete calls ensure_future(future, loop=self) before it checks self.is_running() (which is done in run_forever). Since the loop is already running, it can pick up the task before the RuntimeError is thrown. Of course it doesn't always happen because of the race condition.







        share|improve this answer














        share|improve this answer



        share|improve this answer








        edited Nov 7 at 16:59

























        answered Nov 7 at 16:54









        Chenfeng

        17118




        17118
























            up vote
            1
            down vote













            Exceptions are thrown per thread. The runtime error is raised in a different thread from the event loop. The event loop continues to execute, regardless.



            And wait(1) can sometimes finish it's job because you can get lucky. The asyncio loop internal data structures are not guarded against race conditions caused by using threads (which is why there are specific thread-support methods you should use instead). But the nature of race conditions is such that it depends on the exact order of events and that order can change each time you run your program, depending on what else your OS is doing at the time.



            The run_until_complete() method first calls asyncio.ensure_task() to add the coroutine to the task queue with a 'done' callback attached that will stop the event loop again, then calls loop.run_forever(). When the coroutine returns, the callback stops the loop. The loop.run_forever() call throws the RuntimeError here.



            When you do this from a thread, the task gets added to a deque object attached to the loop, and if that happens at the right moment (e.g. when the running loop is not busy emptying the queue), the running loop in the main thread will find it, and execute it, even if the loop.run_forever() call raised an exception.



            All this relies on implementation details. Different versions of Python will probably exhibit different behaviour here, and if you install an alternative loop (e.g. uvloop), there will almost certainly be different behaviour again.



            If you want to schedule coroutines from a different thread, use asyncio.run_coroutine_threadsafe(); it would :



            from threading import Thread
            import asyncio

            async def wait(t):
            print(f'going to wait {t} seconds')
            await asyncio.sleep(t)
            print(f'waited {t} sec')

            def run(loop):
            asyncio.run_coroutine_threadsafe(wait(2), loop)

            loop = asyncio.get_event_loop()
            t = Thread(target=run, args=(loop,))
            t.start()
            loop.run_until_complete(wait(1))
            t.join()


            The above doesn't actually complete the wait(2) coroutine because the wait(1) coroutine is being run with loop.run_until_complete() so its callback stops the loop again before the 2 second wait is over. But the coroutine is actually started:



            going to wait 1 seconds
            going to wait 2 seconds
            waited 1 sec


            but if you made the main-thread coroutine take longer (with, say, wait(3)) then the one scheduled from the thread would also complete. You'd have to do additional work to ensure that there are no more pending tasks scheduled to run with the loop before you shut it down.






            share|improve this answer























            • That was my vague guess as well, but I'm asking for a more specific answer. I've actually figured it out. Please see my answer.
              – Chenfeng
              Nov 7 at 16:55










            • @Chenfeng: that's exactly what my answer is saying :-)
              – Martijn Pieters
              Nov 7 at 16:59










            • Oh well... I didn't see your edits before I posted mine. Thanks anyway.
              – Chenfeng
              Nov 7 at 17:00






            • 1




              On your additional info: I find it better to call run_forever in a separate thread, and schedule tasks onto the loop from other threads. Once all tasks are completed as indicated by the concurrent.futures.Future returned by run_coroutine_threadsafe, use loop.call_soon_threadsafe(loop.stop) to stop the loop, and use thread.join() to make sure the loop has actually stopped. This ensures that all tasks are done, and all events happen in proper order. No guessing game.
              – Chenfeng
              Nov 7 at 18:15












            • @Chenfeng: closely related: How can you wait for completion of a callback submitted from another thread?. thread.join() is probably fine if all the thread does is run the loop.
              – Martijn Pieters
              Nov 7 at 18:17

















            up vote
            1
            down vote













            Exceptions are thrown per thread. The runtime error is raised in a different thread from the event loop. The event loop continues to execute, regardless.



            And wait(1) can sometimes finish it's job because you can get lucky. The asyncio loop internal data structures are not guarded against race conditions caused by using threads (which is why there are specific thread-support methods you should use instead). But the nature of race conditions is such that it depends on the exact order of events and that order can change each time you run your program, depending on what else your OS is doing at the time.



            The run_until_complete() method first calls asyncio.ensure_task() to add the coroutine to the task queue with a 'done' callback attached that will stop the event loop again, then calls loop.run_forever(). When the coroutine returns, the callback stops the loop. The loop.run_forever() call throws the RuntimeError here.



            When you do this from a thread, the task gets added to a deque object attached to the loop, and if that happens at the right moment (e.g. when the running loop is not busy emptying the queue), the running loop in the main thread will find it, and execute it, even if the loop.run_forever() call raised an exception.



            All this relies on implementation details. Different versions of Python will probably exhibit different behaviour here, and if you install an alternative loop (e.g. uvloop), there will almost certainly be different behaviour again.



            If you want to schedule coroutines from a different thread, use asyncio.run_coroutine_threadsafe(); it would :



            from threading import Thread
            import asyncio

            async def wait(t):
            print(f'going to wait {t} seconds')
            await asyncio.sleep(t)
            print(f'waited {t} sec')

            def run(loop):
            asyncio.run_coroutine_threadsafe(wait(2), loop)

            loop = asyncio.get_event_loop()
            t = Thread(target=run, args=(loop,))
            t.start()
            loop.run_until_complete(wait(1))
            t.join()


            The above doesn't actually complete the wait(2) coroutine because the wait(1) coroutine is being run with loop.run_until_complete() so its callback stops the loop again before the 2 second wait is over. But the coroutine is actually started:



            going to wait 1 seconds
            going to wait 2 seconds
            waited 1 sec


            but if you made the main-thread coroutine take longer (with, say, wait(3)) then the one scheduled from the thread would also complete. You'd have to do additional work to ensure that there are no more pending tasks scheduled to run with the loop before you shut it down.






            share|improve this answer























            • That was my vague guess as well, but I'm asking for a more specific answer. I've actually figured it out. Please see my answer.
              – Chenfeng
              Nov 7 at 16:55










            • @Chenfeng: that's exactly what my answer is saying :-)
              – Martijn Pieters
              Nov 7 at 16:59










            • Oh well... I didn't see your edits before I posted mine. Thanks anyway.
              – Chenfeng
              Nov 7 at 17:00






            • 1




              On your additional info: I find it better to call run_forever in a separate thread, and schedule tasks onto the loop from other threads. Once all tasks are completed as indicated by the concurrent.futures.Future returned by run_coroutine_threadsafe, use loop.call_soon_threadsafe(loop.stop) to stop the loop, and use thread.join() to make sure the loop has actually stopped. This ensures that all tasks are done, and all events happen in proper order. No guessing game.
              – Chenfeng
              Nov 7 at 18:15












            • @Chenfeng: closely related: How can you wait for completion of a callback submitted from another thread?. thread.join() is probably fine if all the thread does is run the loop.
              – Martijn Pieters
              Nov 7 at 18:17















            up vote
            1
            down vote










            up vote
            1
            down vote









            Exceptions are thrown per thread. The runtime error is raised in a different thread from the event loop. The event loop continues to execute, regardless.



            And wait(1) can sometimes finish it's job because you can get lucky. The asyncio loop internal data structures are not guarded against race conditions caused by using threads (which is why there are specific thread-support methods you should use instead). But the nature of race conditions is such that it depends on the exact order of events and that order can change each time you run your program, depending on what else your OS is doing at the time.



            The run_until_complete() method first calls asyncio.ensure_task() to add the coroutine to the task queue with a 'done' callback attached that will stop the event loop again, then calls loop.run_forever(). When the coroutine returns, the callback stops the loop. The loop.run_forever() call throws the RuntimeError here.



            When you do this from a thread, the task gets added to a deque object attached to the loop, and if that happens at the right moment (e.g. when the running loop is not busy emptying the queue), the running loop in the main thread will find it, and execute it, even if the loop.run_forever() call raised an exception.



            All this relies on implementation details. Different versions of Python will probably exhibit different behaviour here, and if you install an alternative loop (e.g. uvloop), there will almost certainly be different behaviour again.



            If you want to schedule coroutines from a different thread, use asyncio.run_coroutine_threadsafe(); it would :



            from threading import Thread
            import asyncio

            async def wait(t):
            print(f'going to wait {t} seconds')
            await asyncio.sleep(t)
            print(f'waited {t} sec')

            def run(loop):
            asyncio.run_coroutine_threadsafe(wait(2), loop)

            loop = asyncio.get_event_loop()
            t = Thread(target=run, args=(loop,))
            t.start()
            loop.run_until_complete(wait(1))
            t.join()


            The above doesn't actually complete the wait(2) coroutine because the wait(1) coroutine is being run with loop.run_until_complete() so its callback stops the loop again before the 2 second wait is over. But the coroutine is actually started:



            going to wait 1 seconds
            going to wait 2 seconds
            waited 1 sec


            but if you made the main-thread coroutine take longer (with, say, wait(3)) then the one scheduled from the thread would also complete. You'd have to do additional work to ensure that there are no more pending tasks scheduled to run with the loop before you shut it down.






            share|improve this answer














            Exceptions are thrown per thread. The runtime error is raised in a different thread from the event loop. The event loop continues to execute, regardless.



            And wait(1) can sometimes finish it's job because you can get lucky. The asyncio loop internal data structures are not guarded against race conditions caused by using threads (which is why there are specific thread-support methods you should use instead). But the nature of race conditions is such that it depends on the exact order of events and that order can change each time you run your program, depending on what else your OS is doing at the time.



            The run_until_complete() method first calls asyncio.ensure_task() to add the coroutine to the task queue with a 'done' callback attached that will stop the event loop again, then calls loop.run_forever(). When the coroutine returns, the callback stops the loop. The loop.run_forever() call throws the RuntimeError here.



            When you do this from a thread, the task gets added to a deque object attached to the loop, and if that happens at the right moment (e.g. when the running loop is not busy emptying the queue), the running loop in the main thread will find it, and execute it, even if the loop.run_forever() call raised an exception.



            All this relies on implementation details. Different versions of Python will probably exhibit different behaviour here, and if you install an alternative loop (e.g. uvloop), there will almost certainly be different behaviour again.



            If you want to schedule coroutines from a different thread, use asyncio.run_coroutine_threadsafe(); it would :



            from threading import Thread
            import asyncio

            async def wait(t):
            print(f'going to wait {t} seconds')
            await asyncio.sleep(t)
            print(f'waited {t} sec')

            def run(loop):
            asyncio.run_coroutine_threadsafe(wait(2), loop)

            loop = asyncio.get_event_loop()
            t = Thread(target=run, args=(loop,))
            t.start()
            loop.run_until_complete(wait(1))
            t.join()


            The above doesn't actually complete the wait(2) coroutine because the wait(1) coroutine is being run with loop.run_until_complete() so its callback stops the loop again before the 2 second wait is over. But the coroutine is actually started:



            going to wait 1 seconds
            going to wait 2 seconds
            waited 1 sec


            but if you made the main-thread coroutine take longer (with, say, wait(3)) then the one scheduled from the thread would also complete. You'd have to do additional work to ensure that there are no more pending tasks scheduled to run with the loop before you shut it down.







            share|improve this answer














            share|improve this answer



            share|improve this answer








            edited Nov 7 at 17:11

























            answered Nov 7 at 16:50









            Martijn Pieters

            691k12923892232




            691k12923892232












            • That was my vague guess as well, but I'm asking for a more specific answer. I've actually figured it out. Please see my answer.
              – Chenfeng
              Nov 7 at 16:55










            • @Chenfeng: that's exactly what my answer is saying :-)
              – Martijn Pieters
              Nov 7 at 16:59










            • Oh well... I didn't see your edits before I posted mine. Thanks anyway.
              – Chenfeng
              Nov 7 at 17:00






            • 1




              On your additional info: I find it better to call run_forever in a separate thread, and schedule tasks onto the loop from other threads. Once all tasks are completed as indicated by the concurrent.futures.Future returned by run_coroutine_threadsafe, use loop.call_soon_threadsafe(loop.stop) to stop the loop, and use thread.join() to make sure the loop has actually stopped. This ensures that all tasks are done, and all events happen in proper order. No guessing game.
              – Chenfeng
              Nov 7 at 18:15












            • @Chenfeng: closely related: How can you wait for completion of a callback submitted from another thread?. thread.join() is probably fine if all the thread does is run the loop.
              – Martijn Pieters
              Nov 7 at 18:17




















            • That was my vague guess as well, but I'm asking for a more specific answer. I've actually figured it out. Please see my answer.
              – Chenfeng
              Nov 7 at 16:55










            • @Chenfeng: that's exactly what my answer is saying :-)
              – Martijn Pieters
              Nov 7 at 16:59










            • Oh well... I didn't see your edits before I posted mine. Thanks anyway.
              – Chenfeng
              Nov 7 at 17:00






            • 1




              On your additional info: I find it better to call run_forever in a separate thread, and schedule tasks onto the loop from other threads. Once all tasks are completed as indicated by the concurrent.futures.Future returned by run_coroutine_threadsafe, use loop.call_soon_threadsafe(loop.stop) to stop the loop, and use thread.join() to make sure the loop has actually stopped. This ensures that all tasks are done, and all events happen in proper order. No guessing game.
              – Chenfeng
              Nov 7 at 18:15












            • @Chenfeng: closely related: How can you wait for completion of a callback submitted from another thread?. thread.join() is probably fine if all the thread does is run the loop.
              – Martijn Pieters
              Nov 7 at 18:17


















            That was my vague guess as well, but I'm asking for a more specific answer. I've actually figured it out. Please see my answer.
            – Chenfeng
            Nov 7 at 16:55




            That was my vague guess as well, but I'm asking for a more specific answer. I've actually figured it out. Please see my answer.
            – Chenfeng
            Nov 7 at 16:55












            @Chenfeng: that's exactly what my answer is saying :-)
            – Martijn Pieters
            Nov 7 at 16:59




            @Chenfeng: that's exactly what my answer is saying :-)
            – Martijn Pieters
            Nov 7 at 16:59












            Oh well... I didn't see your edits before I posted mine. Thanks anyway.
            – Chenfeng
            Nov 7 at 17:00




            Oh well... I didn't see your edits before I posted mine. Thanks anyway.
            – Chenfeng
            Nov 7 at 17:00




            1




            1




            On your additional info: I find it better to call run_forever in a separate thread, and schedule tasks onto the loop from other threads. Once all tasks are completed as indicated by the concurrent.futures.Future returned by run_coroutine_threadsafe, use loop.call_soon_threadsafe(loop.stop) to stop the loop, and use thread.join() to make sure the loop has actually stopped. This ensures that all tasks are done, and all events happen in proper order. No guessing game.
            – Chenfeng
            Nov 7 at 18:15






            On your additional info: I find it better to call run_forever in a separate thread, and schedule tasks onto the loop from other threads. Once all tasks are completed as indicated by the concurrent.futures.Future returned by run_coroutine_threadsafe, use loop.call_soon_threadsafe(loop.stop) to stop the loop, and use thread.join() to make sure the loop has actually stopped. This ensures that all tasks are done, and all events happen in proper order. No guessing game.
            – Chenfeng
            Nov 7 at 18:15














            @Chenfeng: closely related: How can you wait for completion of a callback submitted from another thread?. thread.join() is probably fine if all the thread does is run the loop.
            – Martijn Pieters
            Nov 7 at 18:17






            @Chenfeng: closely related: How can you wait for completion of a callback submitted from another thread?. thread.join() is probably fine if all the thread does is run the loop.
            – Martijn Pieters
            Nov 7 at 18:17




















             

            draft saved


            draft discarded



















































             


            draft saved


            draft discarded














            StackExchange.ready(
            function () {
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53194040%2fwhy-can-asyncio-event-loop-sometimes-finish-a-task-even-when-encountering-runti%23new-answer', 'question_page');
            }
            );

            Post as a guest















            Required, but never shown





















































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown

































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown







            這個網誌中的熱門文章

            Academy of Television Arts & Sciences

            L'Équipe

            1995 France bombings