关于递归函数的Asyncio loop.run_until_complete()

时间:2016-11-29 03:29:54

标签: python recursion python-3.5 future python-asyncio

我正在努力教自己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

在原始函数调用从调用堆栈中弹出之前,我该怎么做才能运行循环?

2 个答案:

答案 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()             

这种方法与具有所有全局变量的良好架构相差甚远,但它捕获了本质。它也符合您的要求

  

希望它运行并完成而不必杀死它

为了实现这一点,你需要有一个协程,当一切都完成后关闭一个循环。