7

我仍然对 asyncio 的工作原理感到非常困惑,所以我试图设置一个简单的示例但无法实现。

以下示例是一个 Web 服务器 (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库也是同步的,但我想这是另一天的问题......

4

3 回答 3

7

该评论包含您响应网络请求和安排稍后生成 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. 这样,即使数据库或图像服务器很慢,请求也不会被阻塞。一切都在同一个异步线程中运行。

如果其中一些还不是异步的,那么它们可以与其他异步函数一起使用,run_in_executor并且应该可以很好地与其他异步函数一起使用。

于 2019-01-28T16:02:31.073 回答
1
  1. 我强烈建议您阅读Brad Solomon 的这篇关于 Python 中并行编程和异步的解释性文章。
  2. 出于异步执行任务的目的,无需在任务完成之前阻止请求 - 我认为最好的选择是使用一个队列,该队列具有从队列模式中消耗的“PDFGenerator”类(也涵盖在文章)
于 2019-02-01T09:49:34.220 回答
0

对于生成大型 PDF 的任务,您可以使用异步任务/作业队列。例如,您可以使用Celery。由于您不想等待任务,而是返回类似“正在生成 PDF,请稍等/秒”的回复。因此,当请求到达“生成 PDF”端点时,您将在 Celery 中创建一个任务,Celery 将异步处理它,完成后,您可以推送到客户端或客户端可以使用任务 ID 使用“任务查找”(或在您实施时)。这是一个示例答案 -如何检查 Celery 中的任务状态?

Celery 和 Asyncio 之间的区别在于,Celery 可以在完全分离的环境中执行任务,并且与服务器的通信是通过像RabbitMQ这样的分布式消息传递来完成的。Asyncio 使用协程来利用阻塞 I/O 时间。它将使用服务器所在的相同环境和处理器。

于 2019-02-04T06:51:49.543 回答