12

This example of aiohttp server in a thread fails with an RuntimeError: There is no current event loop in thread 'Thread-1'. error:

import threading
from aiohttp import web


def aiohttp_server():
    def say_hello(request):
        return web.Response(text='Hello, world')

    app = web.Application(debug=True)
    app.add_routes([web.get('/', say_hello)])
    web.run_app(app)


t = threading.Thread(target=aiohttp_server)
t.start()

How to run a aiohttp server in thread?

dirn
  • 19,454
  • 5
  • 69
  • 74
bux
  • 7,087
  • 11
  • 45
  • 86
  • Maybe this example will help you: https://stackoverflow.com/questions/54650760/python3-and-asyncio-how-to-implement-websocket-server-as-asyncio-instance – Sergio Ivanuzzo Feb 13 '19 at 13:05

4 Answers4

7

Create handler in main thread and manually create an event loop in child thread.

import asyncio
import threading
from aiohttp import web


def aiohttp_server():
    def say_hello(request):
        return web.Response(text='Hello, world')

    app = web.Application(debug=True)
    app.add_routes([web.get('/', say_hello)])
    handler = app.make_handler()
    return handler


def run_server(handler):
    loop = asyncio.new_event_loop()
    asyncio.set_event_loop(loop)
    server = loop.create_server(handler, host='127.0.0.1', port=8089)
    loop.run_until_complete(server)
    loop.run_forever()


t = threading.Thread(target=run_server, args=(aiohttp_server(),))
t.start()

Update

For new aiohttp, use the following, thank @Auyer for notification.

import asyncio
import threading
from aiohttp import web


def aiohttp_server():
    def say_hello(request):
        return web.Response(text='Hello, world')

    app = web.Application()
    app.add_routes([web.get('/', say_hello)])
    runner = web.AppRunner(app)
    return runner


def run_server(runner):
    loop = asyncio.new_event_loop()
    asyncio.set_event_loop(loop)
    loop.run_until_complete(runner.setup())
    site = web.TCPSite(runner, 'localhost', 8080)
    loop.run_until_complete(site.start())
    loop.run_forever()


t = threading.Thread(target=run_server, args=(aiohttp_server(),))
t.start()
Sraw
  • 18,892
  • 11
  • 54
  • 87
  • This example code don't include thread usage example. How can i test it with thread ? – bux Aug 01 '18 at 10:06
  • You mean like that https://0bin.net/paste/Uc6NLG+i+0uugiOQ#ceJJ6bUmB73woV7ogx5vsCyU4JkNenQHeqP-e811j9d ? It produce `RuntimeError: set_wakeup_fd only works in main thread` error. – bux Aug 02 '18 at 12:38
  • Well, you are right. We have to create handler in main thread and then use child thread to execute `loop`. I will update my answer. – Sraw Aug 02 '18 at 12:58
  • Whether you'd do this in the case of threading or not, I dont know, however you can use an `async def` before the `say_hello` to have it async. The `make_handler` is very useful! This helped solve another issue. –  Dec 01 '18 at 13:27
  • `DeprecationWarning: Application.make_handler(...) is deprecated, use AppRunner API instead.` I'm looking for a solution for this warning, and will post her if I find it. – Auyer Aug 19 '19 at 18:18
  • Why is it necessary to create the web runner in the main thread? I tested and it works fine when created in the new thread. (I suppose it's to [handle the requests in the main thread](https://stackoverflow.com/questions/55133510/aiohttp-slowness-with-threading). Why is that useful?) – user202729 Jan 01 '20 at 02:12
2

We must use app.make_handler handler in main thread, example:

import asyncio
import threading
from aiohttp import web

loop = asyncio.get_event_loop()


def say_hello(request):
    return web.Response(text='Hello, world')


app = web.Application(debug=True)
app.add_routes([web.get('/', say_hello)])

handler = app.make_handler()
server = loop.create_server(handler, host='127.0.0.1', port=8080)


def aiohttp_server():
    loop.run_until_complete(server)
    loop.run_forever()


t = threading.Thread(target=aiohttp_server)
t.start()
user4815162342
  • 141,790
  • 18
  • 296
  • 355
bux
  • 7,087
  • 11
  • 45
  • 86
  • Is this a part of question? You shouldn't add an answer instead of editing question. – Sraw Jul 31 '18 at 09:44
  • You are "abusing" the event loop of the main thread, which may work in a simple test code, but it is not a good idea in general. Either create one yourself (as in @Sraw's answer) or just call ```get_event_loop()``` from inside the thread (according to [its source code](https://github.com/python/cpython/blob/3.7/Lib/asyncio/events.py#L632) it will create a new event loop for the thread in this case) – tevemadar Jul 31 '18 at 09:48
  • @tevemadar `get_event_loop()` will only create a new event loop in main thread, see this condition: `isinstance(threading.current_thread(), threading._MainThread)` – Sraw Jul 31 '18 at 09:51
  • @Sraw oops, I thought it was an inner ```if``` – tevemadar Jul 31 '18 at 09:52
  • @Sraw no, it is a response of question. – bux Aug 01 '18 at 10:05
2

There's a new API intended for this use case:

https://docs.aiohttp.org/en/stable/web_advanced.html#application-runners

from aiohttp import web
import asyncio


async def healthz(request):
    return web.Response(text="OK")

app = web.Application()
app.add_routes([web.get("/", healthz)])


async def runner():
    runner = web.AppRunner(app)
    await runner.setup()
    site = web.TCPSite(runner, "localhost", 8080)
    await site.start()


loop = asyncio.get_event_loop()
loop.run_until_complete(runner())

Heron Rossi
  • 681
  • 6
  • 18
0

It's possible to use web.run_app, just create a new event loop (and set handle_signals=False to avoid RuntimeError: set_wakeup_fd only works in main thread):

import threading
import asyncio
from aiohttp import web

def aiohttp_server():
    def say_hello(request):
        return web.Response(text='Hello, world')

    asyncio.set_event_loop(asyncio.new_event_loop())  # create a new event loop here
    app = web.Application(debug=True)
    app.add_routes([web.get('/', say_hello)])
    web.run_app(app, handle_signals=False)
user202729
  • 3,358
  • 3
  • 25
  • 36