簡體   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