我正在努力教自己asyncio,所以我做了一个web scraper的练习。主要工作方法是以下递归函数......
async def get_gids(base_url='http://gd2.mlb.com/components/game/mlb/'):
soup = await get_soup(base_url)
for link in soup.find_all('a'):
dest = link.get('href')
fut = asyncio.Future()
def set_result(out):
try:
fut.set_result(out.result())
except:
pass
if dest.startswith('year_') and dest[YEAR] in VALID_YEARS:
in_fut = asyncio.ensure_future(get_gids(base_url + dest))
in_fut.add_done_callback(set_result)
elif dest.startswith('month_') and dest[MONTH] in VALID_MONTHS:
in_fut = asyncio.ensure_future(get_gids(base_url + dest))
in_fut.add_done_callback(set_result)
elif dest.startswith('day_'):
in_fut = asyncio.ensure_future(get_gids(base_url + dest))
in_fut.add_done_callback(set_result)
elif dest.startswith('gid_'):
if await is_regular_season(base_url + dest):
print('gid: {}'.format(dest))
gids.append(dest)
else:
pass
在run_forever
循环中,效果很好。但是,它希望它运行并完成,而不必我杀死它,像run_unitl_complete(get_gids())
,但这似乎不起作用,因为我只得到第一页。我试过这个......
f = asyncio.wait(get_gids())
loop.run_until_complete(f)
再次没有成功,因为TypeError被引发...... TypeError: expect a list of futures, not coroutine
在原始函数调用从调用堆栈中弹出之前,我该怎么做才能运行循环?
答案 0 :(得分:0)
您应该收到来自asyncio
的警告,因为您当前的代码创建了一个任务,但没有await
它或者返回它来执行其他操作。
import asyncio
def callback(f):
print("Callback: {}".format(f.result()))
async def non_awaiting_coro():
print("Executing non awaiting coroutine")
fut = asyncio.ensure_future(asyncio.sleep(0.25))
fut.add_done_callback(callback)
print("Done executing coroutine")
loop = asyncio.get_event_loop()
loop.run_until_complete(non_awaiting_coro())
返回以下内容:
Executing non awaiting coroutine
Done executing coroutine
Task was destroyed but it is pending!
task: <Task pending coro=<sleep() running at ...>
请注意,未触发回调。如果您等待fut
,协程应该完成:
async def awaiting_coro():
print("Executing coroutine")
fut = asyncio.ensure_future(asyncio.sleep(0.25))
fut.add_done_callback(callback)
await fut
print("Done executing coroutine")
loop = asyncio.get_event_loop()
loop.run_until_complete(awaiting_coro())
返回:
Executing coroutine
Callback: None
Done executing coroutine
答案 1 :(得分:0)
你尝试过一个棘手的事情 - 异步递归。您可能已经注意到有警告:
RuntimeWarning:从来没有等过'coroutine'get_gids'
这意味着你递归创建的协同程序没有在await
的循环中启动。您可以在doc中找到行为的描述:
调用协程不会启动其代码运行 - 调用返回的协程对象在您安排执行之前不会执行任何操作。启动它运行有两种基本方法:从另一个协程调用await coroutine或者从coroutine产生(假设另一个协程已经在运行!),或者使用ensure_future()函数或AbstractEventLoop.create_task()方法调度它的执行。
虽然您只安排了第一个使用run_until_complete
创建的协程。如果循环正在运行,可以安排其他协同程序。
可以通过await
来修复这种情况,但这是一种阻止方法,所以它有点无意义:
async def recursive(arg=0):
if arg < 10:
fut = asyncio.ensure_future(recursive(arg+1))
fut.add_done_callback(lambda out: print(out))
await fut
# Simulation of extra ections
count = 3
while count:
await asyncio.sleep(random.random())
count -= 1
return arg
def main():
loop = asyncio.get_event_loop()
loop.run_until_complete(recursive())
if __name__ == "__main__":
main()
此外,您可以使用全局循环来运行所有协同程序:
LOOP = asyncio.get_event_loop()
TASKS = []
async def recursive(arg=0):
if arg < 10:
fut = asyncio.ensure_future(recursive(arg+1))
TASKS.append(fut)
fut.add_done_callback(lambda out: print(out))
# Simulation of extra actions.
count = 3
while count:
count -= 1
await asyncio.sleep(random.random())
return arg
# Stop the loop when everything is done
async def shutdown():
while not all(i.done() for i in TASKS):
await asyncio.sleep(.1)
LOOP.stop()
def main():
fut = asyncio.ensure_future(recursive())
TASKS.append(fut)
asyncio.ensure_future(shutdown())
LOOP.run_forever()
if __name__ == "__main__":
main()
这种方法与具有所有全局变量的良好架构相差甚远,但它捕获了本质。它也符合您的要求
希望它运行并完成而不必杀死它
为了实现这一点,你需要有一个协程,当一切都完成后关闭一个循环。