我正在尝试使用asyncio从OpenSubtitles中获取一些数据,然后下载该数据中包含信息的文件。我想获取这些数据并使用asyncio同时下载文件。
问题是我要等待列表tasks
中的1个任务完成,然后再开始列表或download_tasks
中的其余任务。这样做的原因是,在self._perform_query()
中,我正在向文件中写入信息,而在self._download_and_save_file()
中,我正在从该文件中读取相同的信息。因此,换句话说,download_tasks
需要等待tasks
中的至少一项任务完成才能开始。
我发现我可以使用asyncio.wait(return_when=FIRST_COMPLETED)
来做到这一点,但是由于某种原因,它无法正常工作:
payloads = [create_payloads(entry) for entry in retreive(table_in_database)]
tasks = [asyncio.create_task(self._perform_query(payload, proxy)) for payload in payloads]
download_tasks = [asyncio.create_task(self._download_and_save_file(url, proxy) for url in url_list]
done, pending = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
print(done)
print(len(done))
print(pending)
print(len(pending))
await asyncio.wait(download_tasks)
输出与预期完全不同。尽管我通过了tasks
,列表asyncio.FIRST_COMPLETED
中的3个任务似乎都已经完成了。为什么会这样?
{<Task finished coro=<SubtitleDownloader._perform_query() done, defined at C:\Users\...\subtitles.py:71> result=None>, <Task finished coro=<SubtitleDownloader._perform_query() done, defined at C:\Users\...\subtitles.py:71> result=None>, <Task finished coro=<SubtitleDownloader._perform_query() done, defined at C:\Users\...\subtitles.py:71> result=None>}
3
set()
0
Exiting
据我所知,self._perform_query()
中的代码不应影响此问题。无论如何,这里只是要确保:
async def _perform_query(self, payload, proxy):
try:
query_result = proxy.SearchSubtitles(self.opensubs_token, [payload], {"limit": 25})
except Fault as e:
raise "A fault has occurred:\n{}".format(e)
except ProtocolError as e:
raise "A ProtocolError has occurred:\n{}".format(e)
else:
if query_result["status"] == "200 OK":
with open("dl_links.json", "w") as dl_links_json:
result = query_result["data"][0]
subtitle_name = result["SubFileName"]
download_link = result["SubDownloadLink"]
download_data = {"download link": download_link,
"file name": subtitle_name}
json.dump(download_data, dl_links_json)
else:
print("Wrong status code: {}".format(query_result["status"]))
就目前而言,我一直在不运行download_tasks
的情况下对其进行测试,但出于上下文的考虑,在此进行了介绍。也许我正在以完全错误的方式来解决这个问题。如果是这样,非常感谢您的投入!
编辑:
问题非常简单,如下所述。 _perform_query
不是一个等待的函数,而是同步运行的。我通过将_perform_query
的文件写入部分编辑为与aiofiles
异步来改变了这一点:
def _perform_query(self, payload, proxy):
query_result = proxy.SearchSubtitles(self.opensubs_token, [payload], {"limit": 25})
if query_result["status"] == "200 OK":
async with aiofiles.open("dl_links.json", mode="w") as dl_links_json:
result = query_result["data"][0]
download_link = result["SubDownloadLink"]
await dl_links_json.write(download_link)
答案 0 :(得分:0)
return_when=FIRST_COMPLETED
不保证只完成一个任务。它保证等待将在任务完成后立即完成,但是其他任务很有可能“同时”完成,对于异步而言,这意味着在事件循环的同一迭代中进行。例如,考虑以下代码:
async def noop():
pass
async def main():
done, pending = await asyncio.wait(
[noop(), noop(), noop()], return_when=asyncio.FIRST_COMPLETED)
print(len(done), len(pending))
asyncio.run(main())
这将打印3 0
,就像您的代码一样。为什么?
asyncio.wait
做两件事:将协程提交到事件循环,并设置回调以在完成任何回调时通知它。但是,noop
协程不包含await
,因此对noop()
的调用都不会挂起,每个都只是做自己的事情并立即返回。结果,所有三个协程实例在事件循环的同一遍内完成。然后wait
被告知所有三个协程均已完成,并尽职尽责地报告了这一事实。
如果您更改noop
以等待随机睡眠,例如将pass
更改为await asyncio.sleep(0.1 * random.random())
,您将获得预期的行为。使用await
时,协程不再同时完成,wait
将在检测到第一个后立即报告。
这揭示了您的代码的真正潜在问题:_perform_query
不用等待。这表明您没有使用异步基础库,或者使用不正确。对SearchSubtitles
的调用可能只是阻塞了事件循环,该事件循环在简单的测试中似乎可以正常工作,但破坏了诸如同步执行任务之类的基本异步功能。