运行 辅助线程上的任何 Web 服务器事件循环

Running any web server event loop on a secondary thread

我们有一个丰富的后端应用程序来处理 messaging/queuing、数据库查询和计算机视觉。我们需要的另一个功能是 tcp 通信——最好是通过 http。重点是:这不是主要 一个网络应用程序。我们希望为不同的目的设置一组 http 通道。是的 - 我们了解包括主题和发布-订阅在内的消息传递:但是直接基于 tcp request/response 也有它的位置。

我查看并试用了六个 python http 网络服务器。它们隐式或显式地描述了对 main 线程上的 运行 和 event loop 的要求。这对我们来说是本末倒置:main 线程已经被其他任务占用,包括协调其他活动。

为了说明预期的结构,我将从我的 aiohttp 特定问题 中提取代码。在那个问题中,我在另一个独立脚本中尝试 运行ning,但在从属线程上:

def runWebapp():
  from aiohttp import web

  async def handle(request):
      name = request.match_info.get('name', "Anonymous")
      text = "Hello, " + name
      return web.Response(text=text)

  app = web.Application()
  app.add_routes([web.get('/', handle),
                  web.get('/{name}', handle)])
  web.run_app(app)

if __name__ == '__main__':
  from threading import Thread
  t = Thread(target=runWebapp)
  t.start()
  print('thread started let''s nap..')
  import time
  time.sleep(50)

这给出了错误:

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

这个错误的意思是"hey you're not running this on the main thread"。

我们可以在逻辑上用这里的其他网络服务器替换aiohttp。这种在辅助线程上请求 Web 服务器的事件处理循环到 运行 的方法是否适用?到目前为止,我还尝试了 cherrypytornadoflask

请注意,我没有尝试过的一个著名网络服务器是 django。但是那个似乎需要围绕 django 预期(/需要?)的目录结构对应用程序进行广泛的重组。我们不想这样做,因为该应用程序有一组其他目的,可以取代拥有 http 服务器的这种杂耍。

看过的一种方法是asyncio。我不明白它是否可以支持 运行 侧线程上的事件循环:如果是这样,那么它就是这个问题的答案。

在任何情况下,是否有任何 Web 服务器明确支持将其事件循环从主线程中分离出来?

您可以在辅助线程上创建和设置事件循环:

asyncio.set_event_loop(asyncio.new_event_loop())

cherrypyflask 没有这个就已经可以工作了; tornado 适用于此。

aiohttp 上,你从它调用 loop.add_signal_handler():

得到另一个错误

ValueError: set_wakeup_fd only works in main thread

您需要跳过它,因为 only the main thread of the main interpreter is allowed to set a new signal handler,这意味着辅助线程上的 Web 服务器 运行 无法直接处理信号以正常退出。

示例:aiohttp

  1. 调用前设置事件循环run_app().
    aiohttp 3.8+ 已经 uses a new event loop in run_app(),所以你可以跳过这个。

  2. 调用run_app()时传递handle_signals=False不添加信号处理程序。

asyncio.set_event_loop(asyncio.new_event_loop())  # aiohttp<3.8
web.run_app(app, handle_signals=False)

示例:龙卷风

调用前设置事件循环app.listen()

asyncio.set_event_loop(asyncio.new_event_loop())
app.listen(8888)
tornado.ioloop.IOLoop.current().start()

任何 Python 程序都是 运行 在单个线程上,即 main。当您创建 Thread 时,并不意味着您的程序已经使用了两个线程。

不幸的是,不可能对每个 Thread 使用不同的事件循环,但可以使用 multiprocessing 而不是 threading 来做到这一点。 它允许为每个 Process.

创建自己的事件循环
from multiprocessing import Process
from aiohttp import web


def runWebapp(port):
    async def handle(request):
        name = request.match_info.get("name", "Anonymous")
        text = "Hello, " + name
        return web.Response(text=text)

    app = web.Application()
    app.add_routes([
        web.get("/", handle),
        web.get("/{name}", handle)
    ])
    web.run_app(app, port=port)


if __name__ == "__main__":
    p1 = Process(target=runWebapp, args=(8080,))
    p2 = Process(target=runWebapp, args=(8081,))
    p1.start()
    p2.start()