Pernahkah kamu menjalankan program Python yang harus memanggil REST API, membaca file, atau mengunduh data dari internet, lalu terasa lambat karena setiap permintaan harus menunggu permintaan sebelumnya selesai? Masalah ini sangat umum ketika program banyak berurusan dengan operasi I/O (input/output) seperti jaringan dan file. Solusinya adalah pemrograman asinkron menggunakan modul bawaan asyncio. Pada tutorial ini, kamu akan belajar konsep async/await, cara kerja event loop, hingga contoh nyata mengunduh banyak URL secara bersamaan — lengkap dengan kode yang bisa langsung kamu jalankan.
Kenapa Program Terasa Lambat? (I/O-bound vs CPU-bound)
Untuk memahami kenapa async berguna, kita perlu membedakan dua jenis beban kerja. CPU-bound adalah pekerjaan yang menghabiskan daya prosesor, misalnya komputasi matematika berat atau pelatihan model machine learning. Sebaliknya, I/O-bound adalah pekerjaan yang sebagian besar waktunya dihabiskan untuk menunggu — menunggu balasan server, menunggu file terbaca dari disk, atau menunggu respons database. Dalam program biasa (sinkron), selama masa menunggu itu terjadi, seluruh program ikut berhenti dan tidak bisa mengerjakan hal lain.
Perhatikan contoh program sinkron berikut. Setiap tugas memanggil time.sleep() yang memblokir seluruh program selama 2 detik:
import time
def tugas(nama, delay):
print(f"{nama} mulai")
time.sleep(delay) # memblokir SELURUH program
print(f"{nama} selesai")
def main():
for nama, delay in [("A", 2), ("B", 2), ("C", 2)]:
tugas(nama, delay)
main()
# Total waktu sekitar 6 detik, padahal tiap tugas cuma 2 detik
Sebagian besar aplikasi web dan skrip data di dunia nyata adalah I/O-bound. Itulah kenapa framework seperti FastAPI dan library aiohttp dibangun di atas asyncio — agar satu proses mampu menangani ribuan permintaan tanpa harus membuat ribuan thread.
Mengenal async dan await
Di Python, fungsi asinkron dideklarasikan dengan kata kunci async def. Memanggil fungsi seperti itu tidak langsung menjalankan isinya — ia mengembalikan sebuah coroutine yang harus dijalankan oleh event loop. Kata kunci await dipakai untuk menunggu hasil sebuah coroutine tanpa memblokir seluruh program.
import asyncio
async def sapa(nama):
print(f"Halo, {nama}!")
await asyncio.sleep(1) # simulasi operasi I/O (tidak memblokir)
print(f"Sampai jumpa, {nama}!")
asyncio.run(sapa("Andi"))
# Halo, Andi!
# (jeda 1 detik)
# Sampai jumpa, Andi!
asyncio.run() adalah cara paling sederhana untuk memulai event loop dan menjalankan satu coroutine utama. Perhatikan await asyncio.sleep(1) — ini meniru operasi I/O yang butuh waktu 1 detik, dan selama menunggu, event loop bebas mengerjakan tugas lain.
Event Loop: Jantung dari asyncio
Event loop adalah pengatur lalu lintas yang menjalankan coroutine satu per satu, lalu berpindah ke coroutine lain setiap kali coroutine yang sedang berjalan melakukan await (menunggu). Karena perpindahan ini sangat ringan (bukan thread sistem operasi), satu event loop bisa menangani ribuan tugas I/O secara bersamaan tanpa biaya yang besar.
Menjalankan Banyak Tugas dengan asyncio.gather
Cara paling mudah untuk menjalankan beberapa coroutine secara bersamaan adalah asyncio.gather(). Fungsi ini menerima banyak coroutine, menjalankannya secara konkuren, dan mengembalikan hasilnya sebagai list sesuai urutan argumen.
import asyncio
async def tugas(nama, delay):
print(f"{nama} mulai")
await asyncio.sleep(delay)
print(f"{nama} selesai")
return nama
async def main():
hasil = await asyncio.gather(
tugas("A", 2),
tugas("B", 2),
tugas("C", 2),
)
print("Hasil:", hasil)
asyncio.run(main())
# A, B, dan C mulai hampir bersamaan
# Total waktu hanya sekitar 2 detik (bukan 6 detik)
# Hasil: ['A', 'B', 'C']
Menjadwalkan Tugas dengan asyncio.create_task
asyncio.create_task() memberi kendali lebih halus: kamu bisa menjadwalkan sebuah coroutine untuk berjalan di latar belakang, lalu melanjutkan pekerjaan lain sebelum menunggu hasilnya. Ini sangat berguna ketika kamu ingin memulai beberapa pekerjaan lebih awal.
import asyncio
async def unduh(url):
await asyncio.sleep(1)
return f"konten {url}"
async def main():
# Jadwalkan tugas, tapi jangan ditunggu dulu
task1 = asyncio.create_task(unduh("https://a.com"))
task2 = asyncio.create_task(unduh("https://b.com"))
task3 = asyncio.create_task(unduh("https://c.com"))
# Kerjakan hal lain selagi unduhan berjalan di latar belakang
print("Menunggu unduhan selesai...")
hasil = await asyncio.gather(task1, task2, task3)
print(hasil)
asyncio.run(main())
Contoh Nyata: Mengunduh Banyak URL Secara Bersamaan
Agar manfaatnya lebih terasa, bandingkan dua versi program yang mengunduh tiga halaman web. Versi pertama memakai urllib secara sinkron, versi kedua memakai aiohttp secara asinkron. Pasang dulu dependensi yang dibutuhkan:
pip install aiohttp
Versi Sinkron (Lambat)
import time
import urllib.request
URLS = [
"https://httpbin.org/delay/1",
"https://httpbin.org/delay/1",
"https://httpbin.org/delay/1",
]
def ambil(url):
with urllib.request.urlopen(url) as resp:
return resp.read()
start = time.perf_counter()
for url in URLS:
ambil(url)
print(f"Selesai dalam {time.perf_counter() - start:.2f} detik")
# Kurang lebih 3 detik — setiap permintaan menunggu giliran
Versi Asinkron (Cepat)
import asyncio
import time
import aiohttp
URLS = [
"https://httpbin.org/delay/1",
"https://httpbin.org/delay/1",
"https://httpbin.org/delay/1",
]
async def ambil(session, url):
async with session.get(url) as resp:
return await resp.text()
async def main():
async with aiohttp.ClientSession() as session:
tugas = [ambil(session, url) for url in URLS]
hasil = await asyncio.gather(*tugas)
print(f"Berhasil mengambil {len(hasil)} halaman")
start = time.perf_counter()
asyncio.run(main())
print(f"Selesai dalam {time.perf_counter() - start:.2f} detik")
# Kurang lebih 1 detik — ketiga permintaan berjalan bersamaan
Perbedaannya jelas: versi asinkron selesai dalam sepertiga waktu karena ketiga permintaan dikirim bersamaan dan event loop mengisi waktu tunggu dengan memproses respons yang sudah tiba. Semakin banyak permintaan, semakin besar penghematan waktunya.
Timeout dan Penanganan Error
Dalam dunia nyata, operasi jaringan bisa macet. Gunakan asyncio.wait_for() untuk membatasi waktu tunggu, dan tangani kegagalan dengan blok try/except.
import asyncio
async def proses_lama():
await asyncio.sleep(10)
return "selesai"
async def main():
try:
hasil = await asyncio.wait_for(proses_lama(), timeout=3)
print(hasil)
except asyncio.TimeoutError:
print("Waktu habis! Proses dibatalkan.")
asyncio.run(main())
# Waktu habis! Proses dibatalkan.
Menjalankan Kode Sinkron yang Memblokir dengan asyncio.to_thread
Tidak semua library Python mendukung async. Untuk fungsi sinkron yang memblokir (misalnya parsing file CSV berukuran besar), pindahkan ke thread terpisah dengan asyncio.to_thread() agar event loop tetap responsif.
import asyncio
import time
def proses_berat():
# Fungsi sinkron yang memblokir, misal membaca file besar
time.sleep(3)
return "data selesai diproses"
async def main():
hasil = await asyncio.to_thread(proses_berat)
print(hasil)
asyncio.run(main())
Kesalahan Umum yang Harus Dihindari
Jangan pernah memanggil time.sleep(), requests.get(), atau operasi blokir lainnya secara langsung di dalam coroutine — itu akan memblokir seluruh event loop dan menghapus semua manfaat async. Gunakan asyncio.sleep(), aiohttp, atau asyncio.to_thread() sebagai gantinya.
Beberapa jebakan lain yang sering ditemui: lupa menambahkan await sehingga coroutine tidak pernah dijalankan, membuat async def untuk fungsi yang murni CPU-bound (tidak akan mempercepat apa pun), dan memanggil asyncio.run() lebih dari satu kali dalam satu program.
Kesimpulan
Dengan asyncio, kamu bisa menulis program Python yang menangani banyak operasi I/O secara bersamaan tanpa thread yang boros memori. Kuncinya ada pada async def, await, asyncio.gather(), asyncio.create_task(), dan asyncio.wait_for(). Kalau kamu ingin memastikan kode async-mu tetap andal, pelajari juga cara menulis pengujian dengan pytest (termasuk plugin pytest-asyncio). Dan jika kamu ingin membangun API modern yang memanfaatkan kekuatan async secara bawaan, lanjutkan ke tutorial Cara Membuat REST API dengan FastAPI. Selamat mencoba, dan semoga programmu makin cepat! 🚀
Apakah asyncio membuat program Python saya lebih cepat?
Untuk pekerjaan I/O-bound (jaringan, file, database), ya — karena waktu tunggu bisa dipakai untuk mengerjakan tugas lain. Namun untuk pekerjaan CPU-bound (komputasi berat), asyncio tidak menambah kecepatan; gunakan multiprocessing atau library seperti NumPy sebagai gantinya.
Apa bedanya asyncio dengan threading?
Threading memakai thread sistem operasi yang relatif berat dan saling bergantian secara preemptif, sehingga butuh kunci (lock) untuk berbagi data. Asyncio berjalan di satu thread dengan perpindahan tugas yang dikendalikan secara kooperatif lewat await, sehingga lebih ringan dan bebas dari race condition untuk operasi I/O.
Kapan sebaiknya tidak menggunakan asyncio?
Hindari asyncio jika programmu sebagian besar CPU-bound, jika library utama yang kamu pakai tidak mendukung async, atau jika basis kode masih sederhana dan belum membutuhkan konkurensi. Mengubah seluruh program menjadi async juga butuh disiplin — semua pemanggilan I/O harus memakai versi yang kompatibel.