asyncio.ensure_future กับ BaseEventLoop.create_task เทียบกับโครูทีนแบบธรรมดา?


101

ฉันเคยเห็นบทแนะนำ Python 3.5 พื้นฐานหลายตัวเกี่ยวกับ asyncio ที่ดำเนินการแบบเดียวกันในรสชาติต่างๆ ในรหัสนี้:

import asyncio  

async def doit(i):
    print("Start %d" % i)
    await asyncio.sleep(3)
    print("End %d" % i)
    return i

if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    #futures = [asyncio.ensure_future(doit(i), loop=loop) for i in range(10)]
    #futures = [loop.create_task(doit(i)) for i in range(10)]
    futures = [doit(i) for i in range(10)]
    result = loop.run_until_complete(asyncio.gather(*futures))
    print(result)

ตัวแปรทั้งสามข้างต้นที่กำหนดfuturesตัวแปรได้ผลลัพธ์เดียวกัน ความแตกต่างเพียงอย่างเดียวที่ฉันเห็นคือเมื่อใช้ตัวแปรที่สามการดำเนินการไม่เป็นไปตามลำดับ (ซึ่งส่วนใหญ่ไม่ควรสำคัญ) มีความแตกต่างอื่น ๆ อีกหรือไม่? มีบางกรณีที่ฉันไม่สามารถใช้ตัวแปรที่ง่ายที่สุด (รายการโครูทีนธรรมดา) ได้หรือไม่?

คำตอบ:


122

ข้อมูลจริง:

เริ่มจาก Python 3.7 asyncio.create_task(coro)ฟังก์ชันระดับสูงถูกเพิ่มเข้ามาเพื่อจุดประสงค์นี้

คุณควรใช้วิธีนี้แทนวิธีอื่นในการสร้างงานจาก coroutimes แต่ถ้าคุณต้องการที่จะสร้างงานจาก awaitable asyncio.ensure_future(obj)พลคุณควรใช้


ข้อมูลเก่า:

ensure_future เทียบกับ create_task

ensure_futureเป็นวิธีการที่จะสร้างจากTask coroutineสร้างงานในรูปแบบต่างๆตามอาร์กิวเมนต์ (รวมถึงการใช้create_taskสำหรับ coroutines และวัตถุในอนาคต)

create_taskAbstractEventLoopเป็นวิธีการที่เป็นนามธรรม ลูปเหตุการณ์ที่แตกต่างกันสามารถใช้ฟังก์ชันนี้ได้หลายวิธี

คุณควรใช้ensure_futureเพื่อสร้างงาน คุณจะต้องใช้create_taskก็ต่อเมื่อคุณจะใช้ประเภทเหตุการณ์วนซ้ำของคุณเอง

อัปเดต:

@ bj0 ชี้ไปที่คำตอบของ Guidoในหัวข้อนี้:

ประเด็นensure_future()คือถ้าคุณมีบางอย่างที่อาจเป็นโครูทีนหรือ a Future(อันหลังมี a Taskเพราะนั่นคือคลาสย่อยของFuture) และคุณต้องการที่จะสามารถเรียกใช้เมธอดบนมันที่กำหนดไว้เท่านั้นFuture(อาจเกี่ยวกับสิ่งเดียว เป็นตัวอย่างที่มีประโยชน์cancel()) เมื่อเป็นFuture(หรือTask) อยู่แล้วจะไม่ทำอะไรเลย เมื่อมันเป็นโครูทีนมันจะห่อด้วยTask.

ถ้าคุณรู้ว่าคุณมี coroutine และคุณต้องการที่จะกำหนดเวลา API create_task()ที่ถูกต้องในการใช้งาน เวลาเดียวที่คุณควรโทรensure_future()คือเมื่อคุณให้ API (เช่น API ส่วนใหญ่ของ asyncio เอง) ที่ยอมรับทั้งโครูทีนหรือ a Futureและคุณต้องทำบางอย่างกับมันที่คุณต้องมีFuture.

และหลังจากนั้น:

ในที่สุดฉันก็ยังเชื่อว่านั่นensure_future()เป็นชื่อที่คลุมเครืออย่างเหมาะสมสำหรับฟังก์ชันการทำงานที่ไม่ค่อยจำเป็น เมื่อมีการสร้างงานจาก coroutine loop.create_task()ที่คุณควรใช้อย่างเหมาะสมชื่อ อาจจะมีนามแฝงว่า asyncio.create_task()?

มันน่าแปลกใจสำหรับฉัน แรงจูงใจหลักของฉันที่จะใช้ensure_futureตลอดมาคือมันเป็นฟังก์ชั่นระดับสูงกว่าเมื่อเทียบกับสมาชิกวงcreate_task(การสนทนามีแนวคิดบางอย่างเช่นการเพิ่มasyncio.spawnหรือasyncio.create_task)

ฉันยังสามารถชี้ให้เห็นว่าในความคิดของฉันมันค่อนข้างสะดวกที่จะใช้ฟังก์ชันสากลที่สามารถจัดการได้Awaitableมากกว่าโครูทีนเท่านั้น

อย่างไรก็ตามคำตอบของ Guido นั้นชัดเจน: "เมื่อสร้างงานจาก coroutine คุณควรใช้ชื่อที่เหมาะสมloop.create_task()"

เมื่อใดควรห่อโครูทีนในงาน?

ห่อโครูทีนในงาน - เป็นวิธีเริ่มโครูทีนนี้ "ในพื้นหลัง" นี่คือตัวอย่าง:

import asyncio


async def msg(text):
    await asyncio.sleep(0.1)
    print(text)


async def long_operation():
    print('long_operation started')
    await asyncio.sleep(3)
    print('long_operation finished')


async def main():
    await msg('first')

    # Now you want to start long_operation, but you don't want to wait it finised:
    # long_operation should be started, but second msg should be printed immediately.
    # Create task to do so:
    task = asyncio.ensure_future(long_operation())

    await msg('second')

    # Now, when you want, you can await task finised:
    await task


if __name__ == "__main__":
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())

เอาท์พุต:

first
long_operation started
second
long_operation finished

คุณสามารถแทนที่asyncio.ensure_future(long_operation())ด้วยเพียงawait long_operation()เพื่อให้รู้สึกถึงความแตกต่าง


3
ตาม Guido คุณควรใช้create_taskหากคุณต้องการวัตถุงานจริงๆซึ่งโดยปกติคุณไม่ควรต้องการ: github.com/python/asyncio/issues/477#issuecomment-268709555
bj0

@ bj0 ขอบคุณสำหรับลิงค์นี้ ฉันอัปเดตคำตอบโดยเพิ่มข้อมูลจากการสนทนานี้
Mikhail Gerasimov

ไม่ensure_futureเพิ่มโดยอัตโนมัติที่สร้างขึ้นTaskเพื่อห่วงเหตุการณ์หลัก?
AlQuemist

@AlQuemist ทุกโคโรทีนอนาคตหรืองานที่คุณสร้างจะถูกผูกเข้ากับลูปเหตุการณ์โดยอัตโนมัติซึ่งจะถูกดำเนินการในภายหลัง โดยดีฟอลต์มันคือลูปเหตุการณ์ปัจจุบันสำหรับเธรดปัจจุบัน แต่คุณสามารถระบุลูปเหตุการณ์อื่นโดยใช้loopอาร์กิวเมนต์คำสำคัญ ( ดูลายเซ็น sure_future )
Mikhail Gerasimov

2
@laycat เราต้องการawaitภายในmsg()เพื่อคืนการควบคุมไปยังเหตุการณ์วนซ้ำในการโทรครั้งที่สอง ลูปเหตุการณ์เมื่อได้รับการควบคุมจะสามารถเริ่มต้นlong_operation()ได้ ทำขึ้นเพื่อสาธิตวิธีensure_futureเริ่มต้นโครูทีนเพื่อดำเนินการพร้อมกันกับโฟลว์การดำเนินการปัจจุบัน
Mikhail Gerasimov

47

create_task()

  • ยอมรับโครูทีน
  • ส่งคืนงาน
  • มันถูกเรียกใช้ในบริบทของลูป

ensure_future()

  • ยอมรับฟิวเจอร์สโครูทีนวัตถุที่รอคอยได้
  • ส่งคืนงาน (หรือในอนาคตหากอนาคตผ่านไป)
  • ถ้าหาเรื่องให้เป็น coroutine จะใช้create_task,
  • สามารถส่งผ่านวัตถุลูปได้

ดังที่คุณเห็น create_task มีความเฉพาะเจาะจงมากขึ้น


async ฟังก์ชันโดยไม่ต้อง create_task หรือ sure_future

asyncฟังก์ชันเรียกใช้อย่างง่ายจะคืนค่าโครูทีน

>>> async def doit(i):
...     await asyncio.sleep(3)
...     return i
>>> doit(4)   
<coroutine object doit at 0x7f91e8e80ba0>

และเนื่องจากgatherใต้ฝากระโปรงทำให้มั่นใจว่า ( ensure_future) args เป็นฟิวเจอร์สensure_futureจึงมีความซ้ำซ้อนอย่างชัดเจน

คำถามที่คล้ายกันความแตกต่างระหว่าง loop.create_task, asyncio.async / sure_future กับ Task คืออะไร?


14

หมายเหตุ: ใช้ได้กับPython 3.7 เท่านั้น (สำหรับ Python 3.5 อ้างถึงคำตอบก่อนหน้านี้ )

จากเอกสารอย่างเป็นทางการ:

asyncio.create_task(เพิ่มในหลาม 3.7) ensure_future()เป็นวิธีที่ดีกว่าสำหรับวางไข่งานใหม่แทน


รายละเอียด:

ตอนนี้ใน Python 3.7 เป็นต้นไปมีฟังก์ชัน wrapper ระดับบนสุด 2 ฟังก์ชัน (คล้ายกัน แต่แตกต่างกัน):

ฟังก์ชัน Wrapper ทั้งสองอย่างนี้จะช่วยให้คุณโทรBaseEventLoop.create_taskได้ ข้อแตกต่างเพียงอย่างเดียวคือensure_futureยอมรับawaitableวัตถุใด ๆและช่วยให้คุณแปลงเป็นอนาคต และคุณสามารถระบุevent_loopพารามิเตอร์ของคุณเองในensure_future. และขึ้นอยู่กับว่าคุณต้องการความสามารถเหล่านั้นหรือไม่คุณก็สามารถเลือกได้ว่าจะใช้กระดาษห่อตัวใด


ฉันคิดว่ามีข้อแตกต่างอีกอย่างที่ไม่ได้บันทึกไว้: หากคุณพยายามเรียก asyncio.create_task ก่อนที่จะรันลูปคุณจะมีปัญหาเนื่องจาก asyncio.create_task คาดว่าจะมีลูปทำงาน คุณสามารถใช้ asyncio.ensure_future ได้ในกรณีนี้เนื่องจากการวนซ้ำไม่ใช่ข้อกำหนด
coelhudo

4

สำหรับตัวอย่างของคุณทั้งสามประเภทดำเนินการแบบอะซิงโครนัส ข้อแตกต่างเพียงอย่างเดียวคือในตัวอย่างที่สามคุณได้สร้างโครูทีนทั้ง 10 ตัวไว้ล่วงหน้าและส่งไปยังลูปพร้อมกัน ดังนั้นรายการสุดท้ายเท่านั้นที่ให้ผลลัพธ์แบบสุ่ม

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.