15

I am having a thread which calls asyncio loops, however this causes the mentioned exception:

RuntimeError: There is no current event loop in thread 'Thread-1'.

This question: RuntimeError: There is no current event loop in thread in async + apscheduler came across the same problem, however they refered to a scheduler which I do not have.

My code is the following:

def worker(ws):
      l1 = asyncio.get_event_loop()
      l1.run_until_complete(ws.start())  


      l2 = asyncio.get_event_loop()
      l2.run_forever()


if __name__ == '__main__':
    ws = Server()
    p = threading.Thread(target=worker,args=(ws,))
    p.start()


    while True:
        try:
            #...do sth
        except KeyboardInterrupt:
            p.join()
            exit() 
Kev1n91
  • 3,553
  • 8
  • 46
  • 96

3 Answers3

25

New thread doesn't have an event loop so you have to pass and set it explicitly:

def worker(ws, loop):
    asyncio.set_event_loop(loop)
    loop.run_until_complete(ws.start())

if __name__ == '__main__':
    ws = Server()
    loop = asyncio.new_event_loop()
    p = threading.Thread(target=worker, args=(ws, loop,))
    p.start()

Also, p.join() won't terminate your script correctly as you never stop the server so your loop will continue running, presumably hanging up the thread. You should call smth like loop.call_soon_threadsafe(ws.shutdown) before joining the thread, ideally waiting for the server's graceful shutdown.

hoefling
  • 59,418
  • 12
  • 147
  • 194
  • 7
    It might be better to use `asyncio.new_event_loop` to allocate a new event loop with in `worker` and set it for the worker thread. *Passing* the main thread's event loop to the worker, as shown here, might lead to both threads trying to share the same event loop instance, which would fail unpredictably. Of course, the above snippet doesn't have that problem, but in a larger code base it is easy to imagine someone trying to e.g. submit something to the event loop from the main thread - especially as the loop continues to be returned by the main thread's calls to `asyncio.get_event_loop()`. – user4815162342 Feb 10 '18 at 22:23
  • @user4815162342 you're absolutely right, I had to pay more attention when adapting question's code... – hoefling Feb 11 '18 at 00:42
  • That fixes the issue, thanks. Note that the code can be made even simpler by allocating the loop *inside* `worker`. That way you don't need to send a `loop` to the `worker` at all, and any confusion as to which loop belongs to which thread is removed, as the event loop is only created (and presumably will be destroyed) inside the worker. – user4815162342 Feb 11 '18 at 07:08
  • But then you will lose control over the loop in the other thread? What would I do if I want to shutdown the running server from the main thread? – hoefling Feb 11 '18 at 11:55
  • Good point. Note that the calling thread has very little control to begin with because the loop is running in a different thread - for example, it cannot even call a simple `loop.stop()` (because doing so would fail to wake up a sleeping loop). The calling thread is only allowed to call `loop.call_soon_threadsafe()` and `asyncio.run_coroutine_threadsafe`. If those are needed, it can create/send the loop, but keep it tucked in a closure that only does those. Showing that might be overkill for this question, since the OP only touches the event loop in the worker, and runs it forever. – user4815162342 Feb 11 '18 at 12:15
  • Yeah, that's why I included the example of controlling the loop via `call_soon_threadsafe` in the original answer, the OP obviously wants to be able to stop the server from keyboard. – hoefling Feb 11 '18 at 20:15
  • Fair enough. +1 from me, at any rate. – user4815162342 Feb 11 '18 at 20:21
  • Thank you really much for all your concerns and ideas, yes - if the program is stopped by str+c the Server should be stopped, too - can someone maybe provide an answer this kept in mind? – Kev1n91 Feb 11 '18 at 20:21
3

I had this issue for running a Bokeh Server in a thread. When I tried to create the server = Server({'/': app}, port=0), I would get this error. From the Tornado documentation I found the following...

Class tornado.platform.asyncio.AnyThreadEventLoopPolicy[source]

Event loop policy that allows loop creation on any thread. The default asyncio event loop policy only automatically creates event loops in the main threads. Other threads must create event loops explicitly or asyncio.get_event_loop (and therefore IOLoop.current) will fail. Installing this policy allows event loops to be created automatically on any thread, matching the behavior of Tornado versions prior to 5.0 (or 5.0 on Python 2).

Usage:

asyncio.set_event_loop_policy(AnyThreadEventLoopPolicy())

http://www.tornadoweb.org/en/stable/asyncio.html#tornado.platform.asyncio.AnyThreadEventLoopPolicy

slfan
  • 8,950
  • 115
  • 65
  • 78
joltman
  • 41
  • 2
2

On Windows with Python 3.7.3, instead of creating the event loop directly in the thread,

I have to:

asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)

Otherwise, the problem would persist.

kakyo
  • 10,460
  • 14
  • 76
  • 140