【问题标题】:Python - How to use FastAPI and uvicorn.run without blocking the thread?Python - 如何在不阻塞线程的情况下使用 FastAPI 和 uvicorn.run?
【发布时间】:2020-05-03 16:08:04
【问题描述】:

我正在寻找将 uvicorn.run() 与 FastAPI 应用程序一起使用但没有 uvicorn.run() 会阻塞线程的可能性。我已经尝试过使用进程、子进程和线程,但没有任何效果。 我的问题是我想从另一个进程启动服务器,该进程应该在启动服务器后继续执行其他任务。另外,我在从另一个进程关闭服务器时遇到问题。

有谁知道如何使用 uvicorn.run() 非阻塞以及如何从另一个进程中阻止它?

问候 LeukoClassic

【问题讨论】:

    标签: python multiprocessing fastapi uvicorn


    【解决方案1】:

    @HadiAlqa​​ttan 给出的方法不起作用,因为uvicorn.run 期望在主线程中运行。会引发signal only works in main thread等错误。

    正确的做法是:

    import contextlib
    import time
    import threading
    import uvicorn
    
    class Server(uvicorn.Server):
        def install_signal_handlers(self):
            pass
    
        @contextlib.contextmanager
        def run_in_thread(self):
            thread = threading.Thread(target=self.run)
            thread.start()
            try:
                while not self.started:
                    time.sleep(1e-3)
                yield
            finally:
                self.should_exit = True
                thread.join()
    
    config = Config("example:app", host="127.0.0.1", port=5000, log_level="info")
    server = Server(config=config)
    
    with server.run_in_thread():
        # Server is started.
        ...
        # Server will be stopped once code put here is completed
        ...
    
    # Server stopped.
    

    使用 pytest 夹具在本地运行实时测试服务器非常方便:

    # conftest.py
    import pytest
    
    @pytest.fixture(scope="session")
    def server():
        server = ...
        with server.run_in_thread():
            yield
    

    致谢:uvicorn#742florimondmanca

    【讨论】:

    • 你是国王
    【解决方案2】:

    这是一个受Aponace uvicorn#1103 启发的替代版本。 uvicorn 维护者希望更多社区参与此问题,因此如果您遇到此问题,请加入讨论。

    conftest.py 文件示例。

    import pytest
    from fastapi.testclient import TestClient
    from app.main import app
    import multiprocessing
    from uvicorn import Config, Server
    
    
    class UvicornServer(multiprocessing.Process):
    
        def __init__(self, config: Config):
            super().__init__()
            self.server = Server(config=config)
            self.config = config
    
        def stop(self):
            self.terminate()
    
        def run(self, *args, **kwargs):
            self.server.run()
    
    
    
    
    @pytest.fixture(scope="session")
    def server():
        config = Config("app.main:app", host="127.0.0.1", port=5000, log_level="debug")
        instance = UvicornServer(config=config)
        instance.start()
        yield instance
        instance.stop()
    
    @pytest.fixture(scope="module")
    def mock_app(server):
        client = TestClient(app)
        yield client
    

    test_app.py 文件示例。

    def test_root(mock_app):
        response = mock_app.get("")
        assert response.status_code == 200
    

    【讨论】:

      【解决方案3】:

      根据Uvicorn 文档,没有以编程方式停止服务器的方法。 相反,您只能通过按 ctrl + c(官方)来停止服务器。

      但是我有一个技巧可以使用multiprocessing 标准库和这三个简单的函数以编程方式解决这个问题:

      • 运行服务器的运行函数。
      • 启动新进程(启动服务器)的启动函数。
      • 加入进程的停止功能(停止服务器)。
      from multiprocessing import Process
      import uvicorn
      
      # global process variable
      proc = None
      
      
      def run(): 
          """
          This function to run configured uvicorn server.
          """
          uvicorn.run(app=app, host=host, port=port)
      
      
      def start():
          """
          This function to start a new process (start the server).
          """
          global proc
          # create process instance and set the target to run function.
          # use daemon mode to stop the process whenever the program stopped.
          proc = Process(target=run, args=(), daemon=True)
          proc.start()
      
      
      def stop(): 
          """
          This function to join (stop) the process (stop the server).
          """
          global proc
          # check if the process is not None
          if proc: 
              # join (stop) the process with a timeout setten to 0.25 seconds.
              # using timeout (the optional arg) is too important in order to
              # enforce the server to stop.
              proc.join(0.25)
      
      


      同样的想法你可以 :


      使用示例:

      from time import sleep
      
      if __name__ == "__main__":
          # to start the server call start function.
          start()
          # run some codes ....
          # to stop the server call stop function.
          stop()
      



      你可以阅读更多关于:

      【讨论】:

      • 感谢您的回答,但您是否尝试过上面的代码?我正在尝试使用 python 3.7 在 Win10 上运行代码,并且在线程中启动 uvicorn 或在新进程中启动它时遇到错误。使用线程的错误如下所示: Traceback (most recent call last): File "C:\Python37\lib\site-packages\uvicorn\main.py", line 565, in install_signal_handlers loop.add_signal_handler(sig, self. handle_exit, sig, None) 文件“C:\Python37\lib\asyncio\events.py”,第 540 行,在 add_signal_handler 中引发 NotImplementedError NotImplementedError 和信号仅在主线程中有效
      • 使用新进程出现以下错误:无法腌制 _thread.RLock 对象。有什么建议可以解决这个问题吗?由于这篇帖子github.com/tiangolo/fastapi/issues/650,最好在进程中运行它,但它对我不起作用。
      • 好的,我自己找到了解决方案。首先,重要的是使用一个新进程在其中启动 uvicorn。然后,如果您想停止 uvicorn,您可以终止或终止该进程。但这似乎不适用于 Windows,至少对我来说它只是在 linux 上工作。为避免“无法腌制 _thread.RLock 对象”的错误,重要的是不要使用带有 self 的方法。因此,例如 run_server(self) 不与新进程一起工作,但 run_server() 是。
      • @Leuko 发布了一个正确修复“主线程”错误的答案
      猜你喜欢
      • 1970-01-01
      • 2017-05-02
      • 1970-01-01
      • 1970-01-01
      • 2014-07-26
      • 2013-09-12
      • 2019-08-28
      • 2020-11-08
      • 1970-01-01
      相关资源
      最近更新 更多