Python asyncio пропустить обработку до возврата функции
Я все еще очень озадачен тем, как работает asyncio, поэтому я пытался показать простой пример, но не смог его достичь.
В следующем примере показан веб-сервер (Quart), который получает запрос на создание большого PDF-файла, затем сервер возвращает ответ перед началом обработки PDF-файла, затем начинает его обработку и позже отправит ссылку на скачивание по электронной почте.
from quart import Quart
import asyncio
import time
app = Quart(__name__)
@app.route('/')
async def pdf():
t1 = time.time()
await generatePdf()
return 'Time to execute : {} seconds'.format(time.time() - t1)
async def generatePdf():
await asyncio.sleep(5)
#sync generatepdf
#send pdf link to email
app.run()
Как бы я пошел по этому поводу? в приведенном выше примере я не хочу, чтобы 5 секунд ждали до возвращения.
Я даже не уверен, что мне нужно asyncio.
И я боюсь, что блокирование серверного приложения после возвращения ответа - это не то, что нужно делать, но и не уверен.
Кроме того, библиотека PDF является синхронной, но я думаю, что это проблема для другого дня...
3 ответа
В комментарии есть все, что вам нужно, чтобы ответить на веб-запрос и запланировать генерацию PDF на будущее.
asyncio.create_task(generatePdf())
Однако, это не очень хорошая идея, если обработка PDF идет медленно, так как она блокирует поток событий asyncio. Т.е. текущий запрос будет получен быстро, но следующий запрос должен будет дождаться завершения генерации PDF.
Правильный способ будет выполнить задачу в исполнителе (особенно ProcessPoolExecutor).
from quart import Quart
import asyncio
import time
from concurrent.futures import ProcessPoolExecutor
app = Quart(__name__)
executor = ProcessPoolExecutor(max_workers=5)
@app.route('/')
async def pdf():
t1 = time.time()
asyncio.get_running_loop().run_in_executor(executor, generatePdf)
# await generatePdf()
return 'Time to execute : {} seconds'.format(time.time() - t1)
def generatePdf():
#sync generatepdf
#send pdf link to email
app.run()
Важно отметить, что, поскольку он работает в другом процессе, generatePdf
не может получить доступ к каким-либо данным без синхронизации. Поэтому передайте все, что нужно функции при вызове функции.
Обновить
Если вы можете рефакторинг generatePdf
функционировать и сделать его асинхронным, это работает лучше всего.
Пример, если сгенерировать PDF выглядит
def generatePdf():
image1 = downloadImage(image1Url)
image2 = downloadImage(image2Url)
data = queryData()
pdfFile = makePdf(image1, image2, data)
link = upLoadToS3(pdfFile)
sendEmail(link)
Вы можете сделать функцию асинхронной, например:
async def generatePdf():
image1, image2, data = await asyncio.gather(downloadImage(image1Url), downloadImage(image2Url), queryData())
pdfFile = makePdf(image1, image2, data)
link = await upLoadToS3(pdfFile)
await sendEmail(link)
Примечание: все вспомогательные функции, такие как downloadImage
, queryData
нужно переписать для поддержки async
, Таким образом, запросы не будут блокироваться, даже если серверы баз данных или изображений работают медленно. Все работает в том же потоке asyncio.
Если некоторые из них еще не являются асинхронными, их можно использовать с run_in_executor
и должен хорошо работать с другими асинхронными функциями.
- Я настоятельно рекомендую ознакомиться с этой пояснительной статьей Брэда Соломона о параллельном программировании и asyncio в python.
- В целях асинхронного выполнения задачи без необходимости блокировать запрос до тех пор, пока задача не будет завершена, я думаю, что лучшим вариантом является использование очереди с классом "PDFGenerator", который использует шаблон очереди (также описанный в статья)
Для вашей задачи, генерируя большой PDF, вы можете использовать асинхронную очередь задач / заданий. В качестве примера вы можете использовать сельдерей. Так как вы не хотите ждать задания, верните ответ типа "генерирование PDF, пожалуйста, подождите минуту / секунду". Поэтому, когда запрос приходит к конечной точке "Сгенерировать PDF", вы создаете задачу в Celery, а Celery обрабатывает ее асинхронно, и после ее завершения вы можете отправить запрос клиенту или клиент может использовать "поиск задачи", используя идентификатор задачи (или как вы реализуете). Вот пример ответа - Как проверить статус задания в Сельдере?
Разница между Celery и Asyncio заключается в том, что Celery может выполнять задачу в полностью изолированной среде, а связь с сервером осуществляется посредством распределенной передачи сообщений, такой как RabbitMQ. Где Asyncio использует сопрограммы, чтобы использовать время блокировки ввода-вывода. Он будет использовать ту же среду и процессоры, где находится ваш сервер.