Operasi jaringan dan berkas sering menunggu lama, dan menunggu dengan memblokir satu thread untuk setiap sambungan cepat menjadi mahal ketika jumlah koneksi bertambah. Nim menjawabnya dengan pemrograman asinkron di pustaka standar, yaitu proc yang diberi pragma {.async.} mengembalikan Future[T], kata await menunggu hasilnya tanpa membekukan program, dan satu event loop mengatur semuanya pada satu thread. Topik ini membangun dua program, yaitu tugas-tugas yang berjalan tumpang tindih, lalu server echo dan kliennya pada satu proses.
Future dan pencapaian pertama#
Proc asinkron dinyatakan dengan pragma {.async.}, dan tubuhnya boleh memakai await untuk menunggu operasi yang lambat. Nilai kembaliannya bukan T melainkan Future[T], yaitu wadah yang kelak berisi hasil, sehingga memanggil proc asinkron seketika mengembalikan kendali ke pemanggilnya sementara pekerjaan berjalan. Program pertama meluncurkan dua tugas dengan jeda berbeda, keduanya dimulai hampir bersamaan, dan hasilnya diambil dengan await pada saat dibutuhkan.
# pesan_async.nim: dua tugas asinkron yang berjalan tumpang tindih
import asyncdispatch
proc tugasPesan(nama: string, jeda: int): Future[string] {.async.} =
await sleepAsync(jeda)
return "tugas " & nama & " selesai"
proc utama() {.async.} =
let a = tugasPesan("a", 10)
let b = tugasPesan("b", 20)
echo await a
echo await b
waitFor utama()# kompilasi dan jalankan program dua tugas asinkron
nim c --hints:off -r pesan_async.nimtugas a selesai
tugas b selesaiKunci program itu ada pada urutan empat baris tubuh proc utama. Pemanggilan tugasPesan dua kali menyalakan dua tugas, dan jeda keduanya berjalan tumpang tindih, sehingga total menunggu mendekati jeda terlama, bukan jumlah keduanya. await tidak memblokir thread, melainkan menyerahkan kendali kembali ke event loop dan meminta dilanjutkan begitu Future-nya terisi, sedangkan waitFor di baris terakhir menjalankan event loop sampai proc asinkron teratas benar-benar selesai. Objek Future itu sendiri dapat diperiksa sebelum ditunggu, karena properti finished menyatakan apakah hasilnya sudah ada dan properti failed menyatakan apakah tubuhnya melempar error. Error yang terjadi di dalam proc asinkron tidak meledak di tempatnya, melainkan tersimpan di Future-nya dan baru muncul kembali pada titik await yang menunggunya, sehingga blok try di sekitar await adalah posisi penanganan yang benar.
Pemilihan fungsi tidur juga menyimpan perbedaan yang mudah terlewat. Proc sleepAsync menyerahkan giliran kepada event loop selama jeda berlangsung, sedangkan sleep dari modul os memblokir seluruh thread tanpa memberi giliran kepada siapa pun, sehingga satu pemanggilan sleep di dalam proc asinkron cukup untuk membekukan semua tugas lain sekaligus.
Struktur event loop#
Modul asyncdispatch menyediakan event loop yang memantau sumber IO pada satu thread. Setiap kali proc asinkron mencapai await, proc itu berhenti di titiknya, kendali kembali ke event loop, dan loop mencari tugas lain yang sumbernya sudah siap. Diagram berikut memperlihatkan siklus itu bagi tiga tugas yang bergantian.
Model satu thread ini menuntut disiplin yang jelas, yaitu tubuh proc asinkron tidak boleh menghabiskan waktu lama tanpa await, karena selama itu tidak ada tugas lain yang mendapat giliran. Pekerjaan berat penghitungan tetap berada di thread biasa, sedangkan pemrograman asinkron ditujukan untuk menunggu, seperti sambungan jaringan dan pembacaan berkas.
Server echo dan klien dalam satu proses#
Contoh jaringan terkecil yang utuh adalah server echo, yaitu server yang mengembalikan setiap pesan yang diterimanya. Program berikut menjalankan server dan klien pada satu proses yang sama dengan alamat 127.0.0.1, sehingga tidak memerlukan jaringan sungguhan. Server asyncCheck serverEcho() diluncurkan tanpa ditunggu, sedangkan klien dijalankan dengan waitFor karena program hanya selesai setelah percakapannya tuntas.
# echo_lokal.nim: server echo dan klien pada 127.0.0.1
import asyncdispatch, asyncnet, strutils
const portLokal = 34711
proc serverEcho() {.async.} =
var server = newAsyncSocket()
server.setSockOpt(OptReuseAddr, true)
server.bindAddr(Port(portLokal), "127.0.0.1")
server.listen()
let klien = await server.accept()
let pesan = await klien.recvLine()
echo "server menerima: ", pesan.strip
await klien.send("balasan: " & pesan & "\n")
klien.close()
server.close()
proc klienKirim() {.async.} =
var klien = newAsyncSocket()
await klien.connect("127.0.0.1", Port(portLokal))
await klien.send("halo echo\n")
let balasan = await klien.recvLine()
echo "klien menerima: ", balasan.strip
klien.close()
asyncCheck serverEcho()
waitFor klienKirim()# kompilasi dan jalankan server echo serta kliennya
nim c --hints:off -r echo_lokal.nimserver menerima: halo echo
klien menerima: balasan: halo echoSambungan memakai modul asyncnet yang menyediakan newAsyncSocket, accept, recvLine, dan send dalam bentuk asinkronnya. recvLine menunggu sampai satu baris lengkap diterima, dan setiap pesan diakhiri \n agar sisi lawannya tahu batas barisnya. Proc asyncCheck meluncurkan proc asinkron tanpa mengambil Future-nya, sehingga kesalahan yang tidak tertangani di dalamnya tetap dilaporkan, dan pilihan ini cocok untuk server yang berjalan sebagai latar sampai program berakhir.
Kesalahan umum#
- Memanggil proc asinkron lalu memakai hasilnya tanpa
await. Nilai yang diterima adalahFuture[string], dan kompilator menolaknya dengan pesan berbentukexpression 'tugasPesan("a", 10)' is of type 'Future[system.string]' and has to be used (or discarded), karena hasil yang belum terisi memang tidak boleh dipakai diam-diam. - Menulis pekerjaan berat di dalam proc asinkron tanpa
await. Selama perhitungan itu berjalan, event loop tidak mendapat giliran sehingga seluruh tugas lain berhenti, dan pemecahannya memindahkan pekerjaan berat ke thread biasa atau memecahnya denganawaitdi antaranya. - Melupakan
waitForpada titik teratas program. Proc asinkron yang dipanggil dari kode biasa hanya mengembalikanFutureyang belum berjalan, sehingga program berakhir tanpa mengerjakan apa pun, danwaitForitulah yang menyalakan event loop sampai selesai.
Ringkasan#
- Proc
{.async.}mengembalikanFuture[T], dipanggil seketika tanpa menunggu, dan hasilnya diambil denganawaityang menyerahkan kendali ke event loop alih-alih memblokir thread. - Event loop
asyncdispatchmenjalankan banyak tugas pada satu thread dengan bergantian di setiap titikawait, sehingga menunggu banyak IO mendekati menunggu yang terlama, bukan jumlah semuanya. - Modul
asyncnetmenyediakan soket asinkron untuk server dan klien,asyncCheckmeluncurkan tugas latar, sedangkanwaitFormenjalankan tugas utama sampai benar-benar selesai.
Referensi#
- Module asyncdispatch — Nim documentation
— Event loop,
waitFor,asyncCheck, dansleepAsyncyang dipakai pada kedua program. - Module asyncnet — Nim documentation
— Soket asinkron beserta
accept,recvLine, dansenduntuk contoh server echo. - Nim Tutorial Part 2: Asynchronous IO
— Penjelasan resmi tentang
Future,await, dan struktur pemrograman asinkron Nim.