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

And*_*ndy 1 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()
Run Code Online (Sandbox Code Playgroud)

我正在尝试做的是见证打印"Downloading .."然后15秒后"仍在下载...",所有这些都是在文件下载开始的时候.我实际看到的是文件的下载仅在代码命中loop.run_until_complete(future)时开始

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

use*_*342 7

当传递协程时,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()
Run Code Online (Sandbox Code Playgroud)

上面的代码有一个不同的问题.当下载时间短于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()
Run Code Online (Sandbox Code Playgroud)