Membuat task asyncio biasanya terasa seperti batas scheduling yang jelas: panggil asyncio.create_task(), simpan task yang dikembalikan, lalu biarkan event loop segera menjalankan coroutine.
Python juga mendukung eager task execution, yaitu coroutine dapat mulai berjalan langsung saat task dibuat. Python 3.14 menyediakan pilihan ini secara langsung melalui keyword eager_start pada asyncio.create_task() dan melalui pembuatan task di task group.
Pendekatan ini dapat menghilangkan overhead scheduling untuk coroutine yang sering selesai tanpa blocking. Namun, ia juga dapat mengubah urutan program dengan dampak yang jauh lebih penting daripada peningkatan performanya.
Aturan pentingnya sederhana: perlakukan eager startup sebagai pilihan semantik eksekusi, bukan sebagai flag optimasi yang tidak berbahaya.
Pahami batas scheduling normal
Pertimbangkan coroutine yang melakukan pekerjaan sinkron sebelum await pertamanya:
import asyncio
async def load_cached(key: str) -> str:
print("coroutine started")
value = cache.get(key)
if value is not None:
return value
return await fetch_remote(key)Dengan deferred scheduling biasa:
task = asyncio.create_task(load_cached("profile"))
print("task created")
result = await taskpemanggilan tersebut membuat dan menjadwalkan task. Coroutine berjalan ketika event loop mendapat kesempatan untuk mengeksekusinya.
Titik scheduling itu dapat diamati. Kode tepat setelah create_task() dapat berjalan sebelum body coroutine dimulai.
Dengan Python 3.14, Anda dapat meminta eager startup untuk task tersebut:
task = asyncio.create_task(
load_cached("profile"),
eager_start=True,
)Task kini dapat mulai mengeksekusi coroutine selama pemanggilan create_task() itu sendiri. Eksekusi berlanjut sampai coroutine pertama kali blocking, return, atau raise.
Jika cache berisi nilainya, load_cached() dapat selesai tanpa pernah dijadwalkan untuk giliran event loop berikutnya.
Gunakan eager startup ketika penyelesaian sinkron umum terjadi
Kandidat paling jelas adalah coroutine yang fast path-nya sering tidak melakukan await apa pun.
Contohnya:
async def get_user(user_id: int) -> User:
cached = users.get(user_id)
if cached is not None:
return cached
user = await database.fetch_user(user_id)
users[user_id] = user
return userJika sebagian besar pemanggilan mengenai in-memory cache, pembuatan task normal menambahkan scheduling event loop meskipun coroutine tidak memiliki pekerjaan asynchronous pada path tersebut.
Eager task dapat menjalankan lookup dan langsung mengembalikan hasil:
task = asyncio.create_task(
get_user(user_id),
eager_start=True,
)
user = await taskJangan menganggap ini otomatis lebih cepat untuk aplikasi secara keseluruhan. Manfaatnya bergantung pada seberapa sering coroutine selesai secara sinkron, berapa banyak task yang dibuat, dan apakah scheduling task signifikan dalam workload. Ukur path sebenarnya sebelum menerapkan eager execution secara luas.
Antisipasi perubahan urutan
Eager startup mengubah kapan user code berjalan.
Misalkan pembuatan task berada di antara perubahan state:
state = []
async def child() -> None:
state.append("child")
async def main() -> None:
state.append("before")
task = asyncio.create_task(child(), eager_start=True)
state.append("after")
await taskKarena child() tidak memiliki await yang blocking, ia dapat menambahkan nilainya sebelum create_task() selesai. Urutan hasilnya dapat menjadi:
before
child
afterKode yang bergantung pada pembuatan task sebagai titik penundaan dapat melihat hasil yang berbeda.
Hal ini berlaku lebih luas daripada list pada contoh sederhana. Kode startup dapat mendaftarkan callback, mengubah shared in-memory state, memperoleh resource, mengeluarkan log, menaikkan metric, atau memanggil application hook. Tinjau semua yang terjadi sebelum suspension point pertama coroutine.
Jangan gunakan pembuatan task sebagai initialization barrier implisit
Pola yang rapuh terlihat seperti ini:
task = asyncio.create_task(worker(), eager_start=True)
registry[task] = metadataJika worker() menganggap task-nya sudah terdaftar, eager execution dapat melanggar asumsi itu karena worker dimulai sebelum assignment terjadi.
Pilih inisialisasi eksplisit. Teruskan state yang diperlukan ke coroutine atau buat entry registry sebelum pekerjaan dimulai jika arsitektur memungkinkan.
Untuk metadata task yang bergantung pada object task yang dikembalikan, rancang coroutine agar tidak membutuhkan metadata tersebut sebelum suspension point pertamanya, atau hindari eager startup untuk task itu.
Pelajaran umumnya adalah kode setelah create_task() tidak dijamin terjadi sebelum body coroutine yang dimulai secara eager.
Jaga bagian sebelum await tetap singkat
Eager execution tidak membuat pekerjaan sinkron menjadi asynchronous.
Coroutine berikut adalah kandidat eager task yang buruk:
async def transform(payload: bytes) -> bytes:
result = expensive_cpu_transform(payload)
await persist(result)
return resultDengan eager startup, expensive_cpu_transform() berjalan sinkron di dalam pembuatan task sampai coroutine mencapai await persist(...). Pekerjaan itu menggunakan thread event loop sama seperti kode Python sinkron lainnya.
Jika pembuatan task terjadi dalam loop, bagian pre-await yang panjang dapat menunda callback dan task yang tidak terkait:
for payload in payloads:
asyncio.create_task(transform(payload), eager_start=True)Jangan menafsirkan eager_start=True sebagai fitur parallelism. Pekerjaan CPU-heavy tetap memerlukan strategi eksekusi yang sesuai, seperti process pool atau interpreter pool ketika workload dan biaya transfer data membenarkannya.
Ingat bahwa eager task dapat sudah selesai
Setelah dibuat secara eager, task yang dikembalikan mungkin sudah selesai.
Artinya, kode seperti ini tidak boleh mengasumsikan state pending:
task = asyncio.create_task(maybe_cached(), eager_start=True)
if task.done():
result = task.result()
else:
result = await taskBiasanya branch tersebut tidak diperlukan; melakukan await pada task yang sudah selesai tetap berfungsi:
task = asyncio.create_task(maybe_cached(), eager_start=True)
result = await taskNamun, lifecycle instrumentation, registry, dan test terkadang memeriksa done(). Pastikan komponen tersebut dapat menangani penyelesaian langsung.
Done callback yang ditambahkan setelah eager completion tetap merupakan bagian dari task API, tetapi jangan membangun correctness dengan asumsi callback akan terpasang sebelum coroutine dapat selesai. Jika observasi harus mengelilingi eksekusi itu sendiri, tempatkan observasi di dalam coroutine atau wrapper coroutine.
Tangani exception melalui kontrak task
Coroutine yang dimulai secara eager dapat raise sebelum pernah blocking:
async def parse_config() -> Config:
return Config.from_text(current_text)Jika parsing gagal selama eager startup, task langsung menjadi failed. Kode yang mengonsumsi task tetap perlu mengambil exception tersebut:
task = asyncio.create_task(parse_config(), eager_start=True)
config = await taskJangan membuat eager task lalu membuangnya hanya karena Anda memperkirakan fast path akan berhasil. Background task memerlukan ownership dan penanganan exception seperti task yang dijadwalkan secara normal.
Structured concurrency sering menjadi pilihan yang lebih baik ketika beberapa task terkait harus berhasil bersama-sama.
Gunakan eager startup dengan TaskGroup secara sengaja
asyncio.TaskGroup memiliki child task-nya, menunggunya, dan mengoordinasikan failure. Pada Python 3.14, create_task() miliknya menerima opsi pembuatan task yang diperlukan untuk meneruskan eager startup ke event loop:
async with asyncio.TaskGroup() as group:
profile = group.create_task(
get_profile(user_id),
eager_start=True,
)
permissions = group.create_task(
get_permissions(user_id),
eager_start=True,
)Ini mempertahankan lifecycle guarantee task group, tetapi tidak mengembalikan deferred ordering. Coroutine pertama dapat berjalan sinkron selama pemanggilan group.create_task() pertama sebelum child kedua bahkan dibuat.
Hal ini penting ketika sibling task berinteraksi melalui shared state. Jangan menganggap semua sibling sudah dibuat sebelum salah satunya mulai berjalan.
Jika simultaneous admission merupakan bagian dari algoritma, eager startup adalah mekanisme yang salah kecuali Anda menambahkan synchronization barrier eksplisit di dalam child.
Pahami default ketika eager_start dihilangkan
Argumen eager_start dapat tidak ditentukan:
asyncio.create_task(coro())Pada Python 3.14, penghilangan argumen ini memungkinkan task factory yang dikonfigurasi pada event loop menentukan mode. Hal ini penting pada aplikasi atau framework yang memasang eager task factory secara global.
Jika call site tertentu membutuhkan deferred execution demi correctness, nyatakan kebutuhan itu secara eksplisit:
asyncio.create_task(coro(), eager_start=False)Demikian juga, gunakan eager_start=True ketika perilaku eager memang disengaja pada call site tersebut.
Nilai eksplisit membuat asumsi eksekusi terlihat saat code review dan mengurangi kejutan ketika konfigurasi task factory berubah di bagian lain aplikasi.
Bedakan eager startup per-task dari eager task factory
Python 3.12 memperkenalkan asyncio.eager_task_factory(). Aplikasi dapat memasangnya pada event loop sehingga task dimulai secara eager secara default:
loop = asyncio.get_running_loop()
loop.set_task_factory(asyncio.eager_task_factory)Ini merupakan perubahan policy yang luas. Dampaknya dapat mengenai pembuatan task di seluruh kode yang menggunakan loop tersebut, termasuk library code yang ditulis dengan asumsi urutan scheduling biasa.
Opsi eager_start per-task di Python 3.14 membuat adopsi terarah lebih mudah. Alih-alih mengubah setiap task yang kompatibel secara implisit, Anda dapat memilih perilaku eager pada call site yang semantik dan karakteristik performanya sudah dipahami.
Global eager factory tetap dapat sesuai dalam sistem yang terkontrol, tetapi uji seluruh aplikasi asynchronous dengan policy tersebut. Permukaan semantiknya jauh lebih besar daripada satu cache lookup yang dioptimalkan.
Pertahankan asumsi context
Pembuatan task juga berinteraksi dengan contextvars. Secara default, task menerima salinan current context, dan asyncio.create_task() dapat menerima argumen context= eksplisit.
Eager startup tidak berarti Anda boleh bergantung pada mutasi context berikutnya agar terlihat oleh child:
request_id.set("request-a")
task = asyncio.create_task(handle(), eager_start=True)
request_id.set("request-b")Context task ditentukan sebagai bagian dari pembuatan task. Rancang state yang scoped ke request berdasarkan batas task tersebut, bukan dengan mengharapkan perubahan parent setelahnya mengalir ke task yang sudah ada.
Jika Anda meneruskan context eksplisit, jaga aturan ownership-nya tetap jelas. Context propagation dan eager execution adalah dua hal terpisah meskipun keduanya dikonfigurasi saat pembuatan task.
Berhati-hati dengan lock dan callback sinkron
Coroutine dapat menjalankan kode Python sinkron apa pun sebelum await pertamanya. Ini termasuk memanggil callback yang diberikan bagian lain aplikasi.
Jika caller membuat eager task ketika sedang mempertahankan invariant sementara, child dapat mengamati sistem dalam state antara tersebut:
items.append(item)
task = asyncio.create_task(notify(item), eager_start=True)
indexes[item.id] = len(items) - 1Jika notify() membaca indexes sebelum await, ia dapat melihat item baru tanpa index-nya.
Perbaiki invariant alih-alih menambahkan asumsi scheduling:
items.append(item)
indexes[item.id] = len(items) - 1
task = asyncio.create_task(notify(item), eager_start=True)Penalaran serupa berlaku di sekitar lock, update in-memory yang menyerupai transaction, dan callback registry. Selesaikan transisi state sinkron sebelum memulai kode yang diizinkan mengamatinya.
Benchmark perbandingan yang tepat
Microbenchmark yang membuat coroutine dengan return konstanta akan menonjolkan overhead scheduling:
async def immediate() -> int:
return 1Ini dapat menunjukkan mekanismenya, tetapi keputusan produksi membutuhkan pengukuran yang representatif.
Pisahkan setidaknya kasus berikut:
- task yang selesai sebelum blocking await pertamanya;
- task yang hampir langsung blocking pada I/O;
- task dengan pekerjaan sinkron berarti sebelum blocking;
- burst yang membuat banyak task dalam satu giliran event loop;
- workload tempat callback atau instrumentation yang sensitif terhadap urutan berjalan saat startup.
Ukur throughput dan latency, tetapi pantau juga responsivitas event loop. Memindahkan pekerjaan ke dalam pemanggilan create_task() dapat mengurangi overhead scheduling sekaligus memperpanjang eksekusi sinkron caller yang tidak terputus.
Uji kedua mode scheduling ketika komponen mendukung keduanya
Concurrency test sering lulus secara kebetulan karena satu urutan scheduler tertentu umum terjadi pada mesin developer.
Jika abstraksi Anda menjanjikan dapat bekerja dengan eager maupun deferred task startup, uji keduanya secara eksplisit:
import asyncio
import pytest
@pytest.mark.parametrize("eager", [False, True])
@pytest.mark.asyncio
async def test_operation(eager):
task = asyncio.create_task(operation(), eager_start=eager)
assert await task == expectedTambahkan juga test untuk batas yang sensitif terhadap urutan:
- penyelesaian sinkron sebelum
create_task()selesai; - coroutine yang raise sebelum
awaitpertamanya; - coroutine yang langsung blocking;
- sibling task group ketika salah satunya selesai secara eager;
- nilai context variable yang ditangkap saat pembuatan;
- cancellation setelah task sudah selesai secara eager;
- instrumentation yang mendaftarkan task setelah pembuatan.
Test sebaiknya memverifikasi invariant aplikasi, bukan menegaskan urutan event loop yang kebetulan, kecuali urutan tersebut memang sengaja menjadi bagian dari desain.
Rencanakan kompatibilitas versi
Constructor asyncio.Task telah mendukung eager startup sejak Python 3.12, dan Python 3.12 juga memperkenalkan eager task factory. Interface tingkat tinggi asyncio.create_task(..., eager_start=...) merupakan tambahan Python 3.14, bersama forwarding keyword pembuatan task yang membuat opsi tersebut tersedia melalui TaskGroup.create_task().
Kode yang menggunakan call signature Python 3.14 tidak akan berjalan tanpa perubahan pada versi lama yang masih didukung.
Untuk library yang mencakup beberapa rilis Python, hindari meneruskan eager_start secara membabi buta kecuali runtime mendukungnya. Lebih penting lagi, jangan diam-diam meniru eager execution dengan menggerakkan coroutine secara langsung; task state, cancellation, context, custom task factory, dan integrasi event loop membuat kontraknya jauh lebih rumit daripada sekadar memanggil send(None).
Buat kontrak scheduling terlihat jelas
Eager task startup paling berguna ketika coroutine memiliki fast path sinkron yang sering terjadi dan aplikasi dapat memperoleh manfaat dari menghindari satu langkah scheduling tambahan.
Biayanya bersifat semantik: kode coroutine dapat berjalan sebelum pembuatan task selesai, task dapat selesai seketika, urutan pembuatan sibling menjadi dapat diamati, dan pekerjaan sinkron sebelum blocking await pertama tetap berada pada giliran event loop caller.
Gunakan eager_start=True ketika konsekuensi tersebut dipahami dan diukur. Gunakan eager_start=False ketika deferred startup merupakan bagian dari correctness. Jika kedua mode dapat diterima, uji keduanya.
Dengan begitu, eager execution berubah dari optimasi global yang mengejutkan menjadi keputusan konkurensi eksplisit pada titik ketika task masuk ke sistem.