Как использовать FastAPI и uvicorn.run без блокировки потока?

Я ищу возможность использовать uvicorn.run () с приложением FstAPI, но без uvicorn.run () блокирует поток. Я уже пробовал использовать процессы, подпроцессы и потоки, но ничего не получалось. Моя проблема в том, что я хочу запустить сервер из другого процесса, который должен продолжить выполнение других задач после запуска сервера. Кроме того, у меня возникают проблемы с закрытием сервера таким образом из другого процесса.

Есть ли у кого-нибудь идеи, как использовать uvicorn.run () без блокировки и как остановить его от другого процесса?

Привет LeukoClassic


person Leuko    schedule 03.05.2020    source источник


Ответы (2)


Согласно документации 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)


Используя ту же идею, вы можете:

  • используйте стандартную библиотеку threading вместо multiprocessing стандартная библиотека.

  • преобразовать эти функции в класс.


Пример использования:

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()



Вы можете узнать больше о:

  • Сервер Uvicorn.
  • multiprocessing стандартная библиотека.
  • threading стандартной библиотеки.
  • Параллелизм, чтобы узнать больше о многопоточности и многопоточности в Python.
person HadiAlqattan    schedule 06.05.2020
comment
Спасибо за ответ, но пробовали ли вы использовать приведенный выше код? Я пытаюсь запустить код на Win10 с помощью python 3.7, и у меня возникают ошибки, либо при запуске uvicorn в потоке, либо при запуске его в новом процессе. Ошибка с использованием потока выглядит так: Traceback (последний вызов последним): файл C: \ Python37 \ lib \ site-packages \ uvicorn \ main.py, строка 565, в 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 и сигнал работает только в основном потоке - person Leuko; 10.05.2020
comment
При использовании нового процесса возникает следующая ошибка: не удается обработать объекты _thread.RLock. Есть предложения, как я могу решить эту проблему? Благодаря этому сообщению github.com/tiangolo/fastapi/issues/650 стало лучше чтобы запустить его в процессе, но у меня это не работает. - person Leuko; 10.05.2020
comment
Хорошо, нашел решение самостоятельно. Во-первых, важно использовать новый процесс, чтобы запустить увикорн в нем. Затем вы можете убить или завершить процесс, если хотите остановить uvicorn. Но, похоже, это не работает с Windows, по крайней мере, для меня это просто работает с Linux. Чтобы избежать ошибки cant pickle _thread.RLock объектов, важно не использовать метод с self. Так, например, run_server (self) не работает с новым процессом, а run_server () работает. - person Leuko; 11.05.2020
comment
@Leuko Отправил ответ с правильным исправлением ошибок основного потока - person Elijas Dapšauskas; 25.10.2020

Подход, предложенный @HadiAlqattan, не будет работать, потому что 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 # 742 от florimondmanca

person Elijas Dapšauskas    schedule 25.10.2020
comment
ты король - person zzfima; 25.11.2020