ปัญหาโฟลว์การดำเนินการ Asyncio

ฉันยังใหม่กับ asyncio ใน python เล็กน้อย ฉันพยายามเรียกใช้โค้ดง่ายๆ นี้ แต่ฉันไม่รู้ว่าทำไมฉันถึงได้รับผลลัพธ์ที่ไม่คาดคิด

สิ่งที่ฉันทำคือในฟังก์ชัน outer ฉันสร้างงานอะซิงก์และจัดเก็บไว้ในอาร์เรย์ tasks ก่อนที่จะรองานเหล่านี้ ฉันเขียนคำสั่งพิมพ์ print("outer") ที่ควรจะรันในทุก ๆ การวนซ้ำ และภายในงาน ฉันเขียนคำสั่งพิมพ์อีกอัน print("inner") ในฟังก์ชัน inner แต่บางอย่างฉันได้รับผลลัพธ์ที่ไม่คาดคิดได้อย่างไร

นี่คือรหัส -

import asyncio


def main():
    loop = asyncio.get_event_loop()
    loop.run_until_complete(outer(loop))
    loop.close()


async def outer(loop):
    tasks = []
    for i in range(0, 5):
        tasks.append(loop.create_task(inner()))

    for task in tasks:
        print("outer")
        await task


async def inner():
    print("inner")
    await asyncio.sleep(0.5)

if __name__ == '__main__':
    main()

นี่คือผลลัพธ์ -

outer
inner
inner
inner
inner
inner
outer
outer
outer
outer

ผลลัพธ์ที่คาดหวังของฉันคือ -

outer
inner
outer
inner
outer
inner
outer
inner
outer
inner

เหตุใด inner ทั้งหมดจึงพิมพ์ก่อน outer ขั้นตอนการดำเนินการที่ถูกต้องของ asyncio คืออะไร ขอบคุณล่วงหน้า.


person shikhar srivastava    schedule 29.03.2020    source แหล่งที่มา
comment
แต่ละงาน/โครูทีน รัน จนถึงคำสั่ง await จากนั้นจะถูกระงับ... ตัวอย่างของคุณคล้ายกับ ตัวอย่าง coroutine ในเอกสารซึ่งมีผลลัพธ์ที่คล้ายกัน   -  person wwii    schedule 29.03.2020
comment
จากเอกสาร - While a Task is running in the event loop, no other Tasks can run in the same thread. When a Task executes an await expression, the running Task gets suspended, and the event loop executes the next Task.   -  person wwii    schedule 29.03.2020
comment
แต่เมื่องานหนึ่งถูกรอคอย เพื่อเริ่มงานใหม่ ลูปเหตุการณ์จะต้องดำเนินการวนซ้ำ for ครั้งถัดไป หากเป็นเช่นนั้น outer ก็ควรจะพิมพ์ออกมา   -  person shikhar srivastava    schedule 30.03.2020


คำตอบ (2)


In async def outer(loop)

for i in range(0, 5):
    tasks.append(loop.create_task(inner()))
  • มีการสร้างงาน inner ใหม่ห้างาน กำหนดเวลา และเริ่มทำงาน
  • ลูปเหตุการณ์จะรันงานที่กำหนดเวลาไว้จนกว่าจะเสร็จสิ้น

หากคุณเพิ่มอีกเล็กน้อยใน inner และ outer มันจะแสดงกระบวนการนี้ได้ดีขึ้น:

async def outer(loop):
    tasks = []
    for i in range(0, 5):
        tasks.append(loop.create_task(inner(i)))
    await asyncio.sleep(3)
    for task in tasks:
        print('outer')
        await task

async def inner(n):
    print(f"inner {n} start")
    await asyncio.sleep(0.5)
    print(f'inner {n} end')
  • ขณะที่ outer กำลังหลับอยู่

    • The first task runs to its await statement
    • ลูปเหตุการณ์จะหยุดการทำงานของงานแรกชั่วคราว
    • ลูปเหตุการณ์ รัน งานที่กำหนดเวลาไว้ถัดไปจนถึงคำสั่ง await
    • สิ่งนี้จะดำเนินต่อไปจนกระทั่งแต่ละงานมีโอกาสที่จะรันถึงคำสั่ง await จากนั้นลูปเหตุการณ์จะเริ่มมองไปรอบๆ เพื่อดูว่ามีงานใดที่เสร็จสิ้นแล้วเพื่อรอสิ่งที่พวกเขารออยู่หรือไม่ - หากทำเสร็จ จะช่วยให้งานเหล่านั้นทำงานได้เพิ่มขึ้นอีก
    • สิ่งนี้จะดำเนินต่อไปจนกว่างานทั้งหมดจะเสร็จสิ้น
  • คุณจะเห็นว่างานทั้งห้าดำเนินการและเสร็จสิ้นก่อนที่ for loop ที่สองจะเริ่มต้นด้วยซ้ำ

  • ในวินาทีสำหรับลูป แต่ละงานได้เสร็จสิ้นแล้ว ดังนั้น await task ไม่มีอะไรต้องรอ และ outer ถูกพิมพ์ห้าครั้งติดต่อกัน

ฉันรู้สึกสับสนเล็กน้อยกับเหตุการณ์ที่ควบคุมทุกอย่าง - ฉันไม่พบเอกสารที่ชัดเจนสำหรับเรื่องนั้น - อาจกล่าวพาดพิงถึงใน create_task docs: Wrap the coro coroutine into a Task and schedule its execution. เมื่อคุณสร้างงาน งานนั้นจะถูกกำหนดเวลาไว้ ฉันเคยเห็นวิดีโอบน pyvideo.org ที่แสดงกระบวนการดังกล่าว แต่น่าเสียดายที่ฉันไม่สามารถค้นหาวิดีโอที่ต้องการเชื่อมโยงได้อย่างรวดเร็ว

person wwii    schedule 30.03.2020
comment
คุณพบลิงก์ pyvideo.org ซึ่งมีการอธิบายกระบวนการทั้งหมดหรือไม่ - person shikhar srivastava; 19.02.2021
comment
@shikharsrivastava มันหลบเลี่ยงฉัน มีมากเกินไปที่จะดู/ดูซ้ำเพื่อหามัน ฉันอาจจะจำบางส่วนของหลายๆ คนผิด โดยคิดว่ามันเป็นหนึ่งเดียวกัน - person wwii; 19.02.2021

loop.create_task(inner()) ทันที จะจัดคิวงาน 5 inner ทั้งหมดให้ทำงาน ไม่ใช่ await task await task ระงับ outer จนกว่างานแรกจะสมบูรณ์เสร็จ นั่นคืออย่างน้อย 0.5 วินาที ในระหว่างนี้ งาน inner ทั้งหมดจะถูกรันครั้งเดียว await รวมถึง print ของพวกเขาด้วย


async def outer(loop):
    tasks = []
    for i in range(0, 5):
        # queues task immediately
        tasks.append(loop.create_task(inner()))

    for task in tasks:
        print("outer")
        # suspends for at least 0.5 seconds
        await task


async def inner():
    # runs immediately
    print("inner")
    await asyncio.sleep(0.5)
person MisterMiyagi    schedule 30.03.2020
comment
อ๋อ!! หากคุณเพิ่ม await asyncio.sleep(10); print('outer done sleeping' หลังลูปที่สร้างงาน คุณจะเห็นว่างานเหล่านั้นทำงานก่อนที่จะมีการรอคอย ขอบคุณ - person wwii; 30.03.2020