在什么条件下才能确保未实际开始未来?

时间:2018-01-27 17:58:16

标签: python python-asyncio

我正在尝试使用子进程中的wget在Python中异步下载文件。我的代码如下所示:

async def download(url, filename):
    wget = await asyncio.create_subprocess_exec(
        'wget', url,
        'O', filename
    )
    await wget.wait()


def main(url):
    loop = asyncio.get_event_loop()
    future = asyncio.ensure_future(download(url, 'test.zip'), loop=loop)
    print("Downloading..")
    time.sleep(15)
    print("Still downloading...")
    loop.run_until_complete(future)
    loop.close()

我正在尝试做的是见证打印“Downloading ..”然后15秒后“仍在下载...”,同时文件下载已经开始。我实际看到的是文件的下载仅在代码命中loop.run_until_complete(future)时开始

我的理解是asyncio.ensure_future应该开始运行download协程的代码,但显然我错过了一些东西。

1 个答案:

答案 0 :(得分:6)

当传递协程时,asyncio.ensure_future将其转换为任务 - 一种知道如何驱动协程的特殊未来 - 并在事件循环中将其排入队列。 “入队”意味着协程内的代码将由调度协程的运行事件循环执行。如果事件循环没有运行,那么任何协同程序都不会有机会运行。告知循环通过调用loop.run_forever()loop.run_until_complete(some_future)来运行。在问题中,事件循环仅在调用time.sleep()之后启动,因此下载开始时间延迟15秒。

time.sleep应该在运行asyncio事件循环的线程中调用从不。正确的睡眠方式是asyncio.sleep,它在等待时产生对事件循环的控制。 asyncio.sleep返回可以提交到事件循环或等待协程的未来:

# ... definition of download omitted ...

async def report():
    print("Downloading..")
    await asyncio.sleep(15)
    print("Still downloading...")

def main(url):
    loop = asyncio.get_event_loop()
    dltask = loop.create_task(download(url, 'test.zip'))
    loop.create_task(report())
    loop.run_until_complete(dltask)
    loop.close()

上面的代码有不同的问题。当下载更短超过15秒时,会导致打印Task was destroyed but it is pending!警告。问题是当下载任务完成并且循环关闭时,report任务从未被取消,它刚被放弃。发生这种情况通常表明存在错误或对asyncio如何工作的误解,因此asyncio会在警告时对其进行标记。

消除警告的显而易见的方法是明确取消report协程的任务,但结果代码最终会变得冗长而不是非常优雅。更简单,更短的修复方法是将report更改为等待下载任务,指定显示“仍在下载...”消息的超时时间:

async def dl_and_report(dltask):
    print("Downloading..")
    try:
        await asyncio.wait_for(asyncio.shield(dltask), 15)
    except asyncio.TimeoutError:
        print("Still downloading...")
        # assuming we want the download to continue; otherwise
        # remove the shield(), and dltask will be canceled
        await dltask

def main(url):
    loop = asyncio.get_event_loop()
    dltask = loop.create_task(download(url, 'test.zip'))
    loop.run_until_complete(dl_and_report(dltask))
    loop.close()