asyncio.ensure_future vs. BaseEventLoop.create_task vs. coroutine sederhana?

97

Saya telah melihat beberapa tutorial dasar Python 3.5 tentang asyncio melakukan operasi yang sama dalam berbagai rasa. Dalam kode ini:

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)

Ketiga varian di atas yang menentukan futuresvariabel mencapai hasil yang sama; satu-satunya perbedaan yang dapat saya lihat adalah bahwa dengan varian ketiga eksekusi rusak (yang seharusnya tidak menjadi masalah dalam banyak kasus). Apakah ada perbedaan lainnya? Apakah ada kasus di mana saya tidak bisa menggunakan varian yang paling sederhana (daftar coroutine biasa)?

crusaderky
sumber

Jawaban:

118

Info sebenarnya:

Mulai dari asyncio.create_task(coro)fungsi tingkat tinggi Python 3.7 ditambahkan untuk tujuan ini.

Anda harus menggunakannya sebagai ganti cara lain untuk membuat tugas dari coroutimes. Namun jika Anda perlu membuat tugas dari arbitrary awaitable, Anda harus menggunakan asyncio.ensure_future(obj).


Info lama:

ensure_future vs. create_task

ensure_futureadalah metode untuk membuat Taskdari coroutine. Ini membuat tugas dengan cara berbeda berdasarkan argumen (termasuk penggunaan create_taskfor coroutine dan objek seperti masa depan).

create_taskadalah metode abstrak AbstractEventLoop. Perulangan peristiwa yang berbeda dapat mengimplementasikan fungsi ini dengan cara yang berbeda.

Anda harus menggunakan ensure_futureuntuk membuat tugas. Kamu akan membutuhkancreate_task hanya jika Anda akan mengimplementasikan jenis loop acara Anda sendiri.

Pembaruan:

@ bj0 menunjuk jawaban Guido tentang topik ini:

Intinya ensure_future()adalah jika Anda memiliki sesuatu yang bisa berupa coroutine atau a Future(yang terakhir menyertakan a Taskkarena itu adalah subkelas dari Future), dan Anda ingin dapat memanggil metode di atasnya yang hanya ditentukan di Future(mungkin tentang satu-satunya contoh yang berguna cancel()). Jika sudah menjadi Future(atau Task) ini tidak melakukan apa-apa; ketika itu adalah coroutine itu membungkusnya dalam a Task.

Jika Anda mengetahui bahwa Anda memiliki coroutine dan Anda ingin menjadwalkannya, API yang tepat untuk digunakan adalah create_task(). Satu-satunya saat Anda harus menelepon ensure_future()adalah saat Anda menyediakan API (seperti kebanyakan API asyncio sendiri) yang menerima coroutine atau a Futuredan Anda perlu melakukan sesuatu yang mengharuskan Anda memiliki Future.

dan nanti:

Pada akhirnya saya masih percaya itu ensure_future()adalah nama yang tidak jelas untuk fungsionalitas yang jarang dibutuhkan. Saat membuat tugas dari coroutine, Anda harus menggunakan nama yang sesuai loop.create_task(). Mungkin harus ada alias untuk itu asyncio.create_task()?

Ini mengejutkan saya. Motivasi utama saya untuk menggunakannya ensure_futureselama ini adalah fungsi tingkat yang lebih tinggi dibandingkan dengan anggota loop create_task(diskusi berisi beberapa ide seperti menambahkan asyncio.spawnatau asyncio.create_task).

Saya juga dapat menunjukkan bahwa menurut saya cukup nyaman menggunakan fungsi universal yang dapat menangani apa saja Awaitabledaripada hanya coroutine.

Namun, jawaban Guido jelas: "Saat membuat tugas dari coroutine Anda harus menggunakan nama yang tepat loop.create_task()"

Kapan coroutine harus dibungkus dalam tugas?

Bungkus coroutine dalam sebuah Tugas - adalah cara untuk memulai coroutine ini "di latar belakang". Berikut contohnya:

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

Keluaran:

first
long_operation started
second
long_operation finished

Anda bisa menggantinya asyncio.ensure_future(long_operation())dengan hanya await long_operation()merasakan perbedaannya.

Mikhail Gerasimov
sumber
3
Menurut Guido, Anda harus menggunakan create_taskjika Anda benar-benar membutuhkan objek tugas, yang biasanya tidak Anda perlukan: github.com/python/asyncio/issues/477#issuecomment-268709555
bj0
@ bj0 terima kasih untuk tautan ini. Saya memperbarui jawaban menambahkan informasi dari diskusi ini.
Mikhail Gerasimov
tidak ensure_futuresecara otomatis menambahkan dibuat Taskke loop acara utama?
AlQuemist
@AlQuemist setiap coroutine, masa depan, atau tugas yang Anda buat secara otomatis terikat ke beberapa event loop, yang akan dijalankan nanti. Secara default adalah saat acara loop untuk thread saat ini, tetapi Anda dapat menentukan loop acara lainnya menggunakan loopargumen kata kunci ( lihat tanda tangan ensure_future ).
Mikhail Gerasimov
2
@laycat kita perlu awaitdalam msg()untuk kembali kontrol ke acara loop pada panggilan kedua. Perulangan peristiwa setelah menerima kontrol akan dapat dimulai long_operation(). Itu dibuat untuk menunjukkan bagaimana ensure_futurememulai coroutine untuk mengeksekusi secara bersamaan dengan aliran eksekusi saat ini.
Mikhail Gerasimov
45

create_task()

  • menerima coroutine,
  • mengembalikan Tugas,
  • itu dipanggil dalam konteks loop.

ensure_future()

  • menerima Futures, coroutines, objek yang menunggu,
  • mengembalikan Task (atau Future jika Future berlalu).
  • jika arg yang diberikan adalah coroutine yang digunakannya create_task,
  • objek loop dapat dilalui.

Seperti yang Anda lihat, create_task lebih spesifik.


async berfungsi tanpa create_task atau sure_future

asyncFungsi pemanggilan sederhana mengembalikan coroutine

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

Dan karena gatherunder the hood memastikan ( ensure_future) bahwa arg adalah futures, secara eksplisit ensure_futuremenjadi mubazir.

Pertanyaan serupa Apa perbedaan antara loop.create_task, asyncio.async / sure_future dan Task?

kwarunek
sumber
13

Catatan: Hanya valid untuk Python 3.7 (untuk Python 3.5 lihat jawaban sebelumnya ).

Dari dokumen resmi:

asyncio.create_task(ditambahkan dengan Python 3.7) adalah cara yang lebih disukai untuk menghasilkan tugas baru daripada ensure_future().


Detil:

Jadi sekarang, di Python 3.7 dan seterusnya, ada 2 fungsi pembungkus tingkat atas (serupa tetapi berbeda):

Nah, sebenarnya kedua fungsi pembungkus ini akan membantu Anda menelepon BaseEventLoop.create_task. Satu-satunya perbedaan adalah ensure_futuremenerima awaitableobjek apa pun dan membantu Anda mengubahnya menjadi Masa Depan. Dan juga Anda dapat memberikan event_loopparameter Anda sendiri di ensure_future. Dan tergantung apakah Anda membutuhkan kemampuan itu atau tidak, Anda dapat memilih pembungkus mana yang akan digunakan.

Yeo
sumber
Saya pikir ada perbedaan lain yang tidak didokumentasikan: jika Anda mencoba memanggil asyncio.create_task sebelum menjalankan loop, Anda akan mendapat masalah karena asyncio.create_task mengharapkan loop yang sedang berjalan. Anda dapat menggunakan asyncio.ensure_future dalam kasus ini, bagaimanapun, karena loop yang berjalan bukanlah suatu keharusan.
coelhudo
4

untuk contoh Anda, ketiga jenis tersebut dijalankan secara asinkron. satu-satunya perbedaan adalah, pada contoh ketiga, Anda membuat 10 coroutine sebelumnya, dan dikirimkan ke loop bersama-sama. jadi hanya yang terakhir yang memberikan keluaran secara acak.

ospider
sumber