Python run_in_executor и забыть? - PullRequest
       58

Python run_in_executor и забыть?

0 голосов
/ 18 февраля 2019

Как я могу настроить функцию блокировки для запуска в исполнителе таким образом, чтобы результат не имел значения, поэтому основной поток не должен ждать или замедляться им.

Чтобы бытьЧестно говоря, я не уверен, что это даже правильное решение для этого, все, что я хочу, это чтобы какой-то тип очереди обработки был отделен от основного процесса, чтобы он не блокировал серверное приложение от возврата запросов, как этот типвеб-сервер запускает одного работника для множества запросов.

Желательно, чтобы я держался подальше от таких решений, как Celery, но если это наиболее оптимальный вариант, я бы хотел его изучить.

Контекст здесьэто асинхронный веб-сервер, который генерирует PDF-файлы с большими изображениями.

app = Sanic()
#App "global" worker
executor = ProcessPoolExecutor(max_workers=5)

app.route('/')
async def getPdf(request):
  asyncio.create_task(renderPdfsInExecutor(request.json))
  #This should be returned "instantly" regardless of pdf generation time
  return response.text('Pdf being generated, it will be sent to your email when done')

async def renderPdfsInExecutor(json):
  asyncio.get_running_loop.run_in_executor(executor, syncRenderPdfs, json)

def syncRenderPdfs(json)
  #Some PDF Library that downloads images synchronously
  pdfs = somePdfLibrary.generatePdfsFromJson(json)
  sendToDefaultMail(pdfs)

Приведенный выше код выдает ошибку (Да, он работает от имени администратора):

PermissionError [WinError 5] Access denied
Future exception was never retrieved

Дополнительный вопрос:Получу ли я что-нибудь, запустив цикл asyncio внутри исполнителя?Таким образом, если он обрабатывает несколько запросов PDF одновременно, он распределяет обработку между ними.Если да, то как мне это сделать?

Ответы [ 2 ]

0 голосов
/ 21 февраля 2019

Вы можете посмотреть на asyncio.gather (* tasks) для параллельного запуска нескольких.

0 голосов
/ 18 февраля 2019

Хорошо, поэтому, во-первых, есть недоразумение.Это

async def getPdf(request):
    asyncio.create_task(renderPdfsInExecutor(request.json))
    ...

async def renderPdfsInExecutor(json):
    asyncio.get_running_loop.run_in_executor(executor, syncRenderPdfs, json)

является избыточным.Достаточно сделать

async def getPdf(request):
    asyncio.get_running_loop.run_in_executor(executor, syncRenderPdfs, request.json)
    ...

или (поскольку вы не хотите ждать) еще лучше

async def getPdf(request):
    executor.submit(syncRenderPdfs, request.json)
    ...

Теперь проблема, которую вы получаете, заключается в том, что syncRenderPdfs throws PermissionError,Он не обрабатывается, поэтому Python предупреждает вас: «Привет, какой-то фоновый код выдал ошибку. Но этот код никем не принадлежит, так что, черт возьми?».Вот почему вы получаете Future exception was never retrieved. У вас проблема с самой библиотекой pdf , а не с asyncio.После того, как вы исправите эту внутреннюю проблему, это также хорошая идея, чтобы быть в безопасности:

def syncRenderPdfs(json)
    try:
        #Some PDF Library that downloads images synchronously
        pdfs = somePdfLibrary.generatePdfsFromJson(json)
        sendToDefaultMail(pdfs)
    except Exception:
        logger.exception('Something went wrong')  # or whatever

Ваша проблема с «отказом в разрешении» - это совсем другое дело, и вы должны отладить ее и / или опубликовать отдельный вопрос для этого.

Что касается последнего вопроса: да, исполнитель будет ставить в очередь и равномерно распределять задачи между работниками.

РЕДАКТИРОВАТЬ: Как мы уже говорили в комментариях, реальная проблема можетбыть в среде Windows, в которой вы работаете.Или, точнее, с ProcessPoolExecutor, т.е. процессы нереста могут изменять разрешения.Я советую использовать ThreadPoolExecutor, предполагая, что он отлично работает на платформе.

...