79128816

Date: 2024-10-26 15:13:09
Score: 2
Natty:
Report link

You already know the reason: Thread.join() blocks the event loop. So I will go straight to the solution.

  1. If you really need asynchronous Thread.join(), you're out of luck. Because in this case you need to start a separate thread to wait:

    await asyncio.to_thread(t.join)
    

    or

    await asyncio.get_running_loop().run_in_executor(executor, t.join)
    

    So due to the way threads are implemented in the threading module. And of course it has disadvantages: you can't cancel a started thread. You will have to forget about asyncio timeouts, because every second attempt to do Thread.join() will start another thread. You will actually get a thread leak!

    Both alternative and combined solutions will be to poll that the thread is alive. This way you will be wasting CPU resources. If you start an extra thread to wait, so the return will be fast. But if it's not, the return will be as long as the timeout you set.

  2. If you have to wait for an event from another thread, you're in luck. This question has already been asked on StackOverflow. Just use any solution from there and call the set() method in your thread.

  3. If you want to wait for something to be processed by multiple threads, you need a proper notification mechanism. I suggest not reinventing the wheel, as there is already aiologic.CountdownEvent (I'm the creator of aiologic).

    import time
    import asyncio
    
    from threading import Thread
    
    from aiologic import CountdownEvent
    
    
    def work(event):
        try:
            time.sleep(1)
        finally:
            event.down()  # -1 thread to wait
    
    
    async def main():
        event = CountdownEvent()
    
        for _ in range(4):
            event.up()  # +1 thread to wait
    
            Thread(target=work, args=[event]).start()
    
        print("before")
    
        await event  # wait for all threads
    
        print("after")
    
    
    asyncio.run(main())
    

    This is a universal synchronization primitive with which you can wait for what you want. The up() and down() methods respectively increase and decrease the countdown event value. When it's zero, all waiting tasks are woken up.

  4. If you try to do what existing synchronization primitives usually do, don't do it. Use the synchronization primitives from aiologic, such as aiologic.Lock, aiologic.Semaphore, or aiologic.Condition. They just work.

Reasons:
  • Blacklisted phrase (1.5): any solution
  • Blacklisted phrase (1): StackOverflow
  • Long answer (-1):
  • Has code block (-0.5):
  • Low reputation (1):
Posted by: Ilya Egorov