繁体   English   中英

python asyncio 动态添加任务

[英]python asyncio add tasks dynamically

我正在 python 中学习 asyncio,我想尝试使用 asyncio。 我的目标是连续读取用户的输入并使用该输入来创建需要异步运行的作业/任务。

import asyncio
import os
loop = asyncio.get_event_loop()

async def action():
    inp = int(input('enter: '))
    await asyncio.sleep(inp)
    os.system(f"say '{inp} seconds waited'")

async def main():
    while True:
        await asyncio.ensure_future(action())

try:
    asyncio.run(main())
except Exception as e:
    print(str(e))
finally:
    loop.close()

我搞砸了一些事情,我想知道如何实现它。 每次用户输入一个数字,脚本需要休眠给定的时间,然后说出它已经等待。 整个事情需要同时进行。 如果用户输入 100 作为输入,脚本应该启动一个任务休眠 100 秒,但是在用户端,它需要在用户输入时再次请求输入。

您的代码的主要问题是您直接在异步 function 中调用了input() input本身是一个阻塞 function 并且在读取换行符或文件结尾之前不会返回。 这是一个问题,因为 Python 异步代码仍然是单线程的,如果存在阻塞 function,则不会执行其他任何操作。 在这种情况下,您需要使用run_in_executor

您的代码的另一个问题虽然与您的问题没有直接关系,但您混合了 pre-python3.7 调用事件循环的方式和 python3.7+ 方式。 根据文档asyncio.run使用。 如果要使用 pre 3.7 调用循环的方式,正确的方式是

loop = asyncio.get_event_loop()
loop.run_until_complete(main())

或者

loop = asyncio.get_event_loop()
asyncio.ensure_future(main())
loop.run_forever()

由于您的main()中有一段while True ,因此run_until_completerun_forever之间没有区别。

最后,在main() () 中使用ensure_future()毫无意义。 ensure_future的重点是提供“正常”(即非异步)function 一种将事物安排到事件循环中的方法,因为它们不能使用await关键字。 使用ensure_future的另一个原因是,如果您想安排许多具有高 io-bounds 的任务(例如网络请求)而不等待它们的结果。 由于您正在await调用 function ,因此自然没有使用ensure_future的意义。

这是修改后的版本:

import asyncio
import os

async def action():
    loop = asyncio.get_running_loop()
    inp = await loop.run_in_executor(None, input, 'Enter a number: ')
    await asyncio.sleep(int(inp))
    os.system(f"say '{inp} seconds waited'")


async def main():
    while True:
        await action()


asyncio.run(main())

在这个版本中,在输入用户输入之前,代码执行在await action()await loop.run_in_executor()之间交替执行。 当没有安排其他任务时,事件循环大部分是空闲的。 但是,当有事情被安排时(使用await sleep()模拟),那么控制权自然会转移到被安排的长时间运行的任务上。

Python 异步编程的一个关键是您必须确保偶尔将控制权转移回事件循环,以便可以运行其他计划的事情。 每当遇到await时都会发生这种情况。 在您的原始代码中,解释器卡在input()并且永远没有机会将 go 返回事件循环,这就是为什么在提供用户输入之前不会执行其他计划任务的原因。

你可以尝试这样的事情:

import asyncio


WORKERS = 10


async def worker(q):
    while True:
        t = await q.get()
        await asyncio.sleep(t)
        q.task_done()

        print(f"say '{t} seconds waited'")


async def main():
    q = asyncio.Queue()

    tasks = []

    for _ in range(WORKERS):
        tasks.append(asyncio.create_task(worker(q)))

    print(f'Keep inserting numbers, "q" to quit...')

    while (number := await asyncio.to_thread(input)) != "q":
        q.put_nowait(int(number))

    await q.join()

    for task in tasks:
        task.cancel()

    await asyncio.gather(*tasks, return_exceptions=True)


if __name__ == "__main__":
    asyncio.run(main())

测试:

$ python test.py
Keep inserting numbers, "q" to quit...
1
say '1 seconds waited'
3
2
1
say '1 seconds waited'
say '2 seconds waited'
say '3 seconds waited'
q

注意:由于使用了一些新语法 ( := ) 和 function ( asyncio.to_thread ),因此需要Python 3.9+。

import asyncio

async def aworker(q):
    ''' Worker that takes numbers from the queue and prints them '''    
    while True:
        t = await q.get() # Wait for a number to be put in the queue
        print(f"{t} received {asyncio.current_task().get_coro().__name__}:{asyncio.current_task().get_name()}")
        await asyncio.sleep(t)
        q.task_done()
        print(f"waited for {t} seconds in {asyncio.current_task().get_coro().__name__}:{asyncio.current_task().get_name()}")

async def looper():
    ''' Infinite loop that prints the current task name '''
    i = 0
    while True:
        i+=1
        await asyncio.sleep(1)
        print(f"{i} {asyncio.current_task().get_name()}")
        names = []
        for task in asyncio.all_tasks():
            names.append(task.get_name())
        print(names)

async def main():
    q = asyncio.Queue()
    tasks = []
    
    # create two worker tasks and one infinitely looping task
    tasks.append(asyncio.create_task(aworker(q), name="aworker 1")) # Create a worker which handles input from the queue
    tasks.append(asyncio.create_task(aworker(q), name="aworker 2")) # Create another worker which handles input from the queue
    tasks.append(asyncio.create_task(looper(),name="looper")) # Create a looper task which prints the current task name and the other running tasks
    
    for task in tasks:
        # print the task names thus far
        print(task.get_name())

    print(f'Keep inserting numbers, "q" to quit...')
    
    ''' asyncio.thread names itself Task-1 '''
    while (number := await asyncio.to_thread(input)) != "q":
        try:
            q.put_nowait(int(number))
        except ValueError:
            print("Invalid number")

    await q.join()

    for task in tasks:
        task.cancel()

    await asyncio.gather(*tasks, return_exceptions=True)

if __name__ == "__main__":
    
    asyncio.run(main())

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM