我正在尝试使用子进程中的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
协程的代码,但显然我错过了一些东西。
答案 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()