Pembatalan pada .NET bersifat kooperatif (cooperative cancellation). Artinya, operasi yang dibatalkan tidak dihentikan secara paksa, melainkan diberi sinyal untuk berhenti dan menentukan sendiri titik aman untuk mengakhiri prosesnya. Topik ini membahas penggunaan CancellationToken dalam operasi asinkron, mulai dari peran CancellationTokenSource, pola meneruskan token ke lapisan pemrosesan di bawahnya, hingga penanganan OperationCanceledException. Setelah mempelajari topik ini, pembaca diharapkan mampu menghentikan pekerjaan asinkron yang tidak lagi diperlukan tanpa meninggalkan pekerjaan yang masih berjalan di latar belakang atau kesalahan yang tidak tertangani.
Sumber dan token pembatalan#
Pembatalan berputar pada dua tipe. CancellationTokenSource adalah pemilik keputusan, yaitu objek yang memegang tombol pembatalan melalui metode Cancel. CancellationToken adalah pandangan baca dari keputusan itu, yaitu struktur ringan yang diteruskan ke metode dan hanya dapat memeriksa apakah pembatalan sudah diminta. Satu sumber dapat melahirkan token untuk banyak metode, sedangkan satu metode dapat menerima token dari sumber mana pun.
Karena sifat kooperatifnya, token tidak menghentikan apa pun dengan sendirinya. Pembatalan baru terjadi apabila kode pekerja memeriksa token melalui properti IsCancellationRequested, memanggil ThrowIfCancellationRequested, atau mendelegasikan pemeriksaan itu kepada API yang menerima token seperti Task.Delay. Pemisahan dua tipe ini menjaga arah keputusan tetap satu, yaitu sumber yang membatalkan dan pekerja yang menaati, sehingga pekerja tidak dapat membatalkan pemanggilnya.
Konvensi .NET menempatkan token pada parameter terakhir metode, dan disiplin meneruskannya berlaku untuk seluruh rantai panggilan. Token yang diterima lapisan atas dioper ke lapisan bawah sebagai argumen, sehingga API pembacaan berkas, permintaan jaringan, dan penantian lain yang menerima token ikut berhenti bersamaan. Metode Register pada token juga mampu mendaftarkan callback yang dipanggil ketika pembatalan diminta, dan daftar itu berguna untuk melepas sumber daya yang tidak mendukung token secara langsung.
Membatalkan pekerjaan yang sedang berjalan#
Program berikut menjalankan dua pekerjaan bersamaan, yaitu unduhan yang lama dan pembacaan catatan yang cepat. Setelah catatan selesai, unduhan tidak lagi diperlukan sehingga diminta berhenti. Salin programnya ke berkas Program.cs, lalu jalankan dengan perintah dotnet run --file Program.cs.
// Program pembatalan: pekerjaan cepat menentukan nasib pekerjaan panjang.
var cts = new CancellationTokenSource();
CancellationToken token = cts.Token;
async Task UnduhArsipAsync(CancellationToken token)
{
Console.WriteLine("Unduhan: mulai");
await Task.Delay(5000, token);
Console.WriteLine("Unduhan: selesai");
}
async Task BacaCatatanAsync()
{
Console.WriteLine("Pembacaan catatan: mulai");
await Task.Delay(50);
Console.WriteLine("Pembacaan catatan: selesai");
}
Task unduhan = UnduhArsipAsync(token);
Task catatan = BacaCatatanAsync();
await catatan;
Console.WriteLine("Catatan cukup, unduhan tidak lagi diperlukan");
cts.Cancel();
try
{
await unduhan;
}
catch (OperationCanceledException)
{
Console.WriteLine("Unduhan dibatalkan dengan rapi");
}Unduhan: mulai
Pembacaan catatan: mulai
Pembacaan catatan: selesai
Catatan cukup, unduhan tidak lagi diperlukan
Unduhan dibatalkan dengan rapiAlur keluarannya dapat dilacak dengan pasti. Kedua pekerjaan mencetak baris mulainya karena keduanya berjalan hingga await pertama, kemudian pembacaan catatan selesai lebih dahulu karena hanya berdurasi 50 milidetik. Pemanggilan cts.Cancel() lalu membatalkan Task.Delay yang sedang menunggu di dalam UnduhArsipAsync, sehingga await atas tugas unduhan melempar OperationCanceledException yang ditangkap blok catch. Pekerjaan itu berhenti jauh sebelum 5000 milidetik penuh tanpa ada thread yang menunggu terblokir.
Demonstrasi ini menuntut peran dua pihak sekaligus. Pemanggil menyimpan token yang sama dengan token yang diterima pekerjaan, dan pekerjaan meneruskan token ke setiap API yang menunggu, dalam hal ini Task.Delay dengan argumen token. Apabila Task.Delay dipanggil tanpa token, pembatalan tidak pernah terjadi karena tidak ada yang memeriksanya.
Pemeriksaan token di dalam pekerjaan#
Operasi yang dibagi menjadi banyak langkah memeriksa token di antara langkah-langkahnya. Pemeriksaan paling ringkas adalah memanggil ThrowIfCancellationRequested pada titik-titik aman, misalnya sebelum setiap batch pekerjaan. Metode itu melempar OperationCanceledException apabila pembatalan sudah diminta, dan exception ini adalah sinyal resmi bahwa pekerjaan dihentikan, bukan tanda bahwa pekerjaan gagal.
Keadaan pembatalan bersifat kekal, yaitu sekali diminta, keadaan itu tidak pernah kembali. Program pendek berikut memperlihatkan akibatnya terhadap pekerjaan yang dimulai setelah pembatalan.
// Contoh: pemeriksaan token di awal pekerjaan.
var cts = new CancellationTokenSource();
CancellationToken token = cts.Token;
async Task ProsesAsync(CancellationToken token)
{
token.ThrowIfCancellationRequested();
Console.WriteLine("Pekerjaan dimulai");
await Task.Delay(50, token);
Console.WriteLine("Pekerjaan selesai");
}
await ProsesAsync(token);
cts.Cancel();
try
{
await ProsesAsync(token);
}
catch (OperationCanceledException)
{
Console.WriteLine("Pekerjaan kedua tidak dijalankan karena token sudah dibatalkan");
}Pekerjaan dimulai
Pekerjaan selesai
Pekerjaan kedua tidak dijalankan karena token sudah dibatalkanPekerjaan pertama berjalan normal karena token masih segar. Setelah cts.Cancel() dipanggil, panggilan kedua terhadap metode yang sama berhenti di baris pertamanya, yaitu pada ThrowIfCancellationRequested, sebelum pekerjaan apa pun dilakukan. Pola ini berguna untuk menolak pekerjaan baru begitu pembatalan diminta.
Satu sumber pembatalan juga dapat diperluas dari beberapa sumber. CancellationTokenSource.CreateLinkedTokenSource membuat token gabungan yang terbatal apabila salah satu sumber asalnya terbatal, misalnya pembatalan dari server dan pembatalan dari pengguna.
// Contoh: token gabungan dari dua sumber pembatalan.
var ctsServer = new CancellationTokenSource();
var ctsPengguna = new CancellationTokenSource();
using CancellationTokenSource gabungan =
CancellationTokenSource.CreateLinkedTokenSource(ctsServer.Token, ctsPengguna.Token);
ctsPengguna.Cancel();
Console.WriteLine($"Token gabungan dibatalkan: {gabungan.Token.IsCancellationRequested}");
Console.WriteLine($"Token server ikut terbatal: {ctsServer.Token.IsCancellationRequested}");Token gabungan dibatalkan: True
Token server ikut terbatal: FalseKeluarannya menunjukkan aliran satu arah dari sumber ke gabungan. Pembatalan ctsPengguna merembes ke token gabungan, sedangkan token ctsServer tidak terpengaruh karena sumbernya belum dibatalkan.
Kesalahan umum#
Tiga kesalahan berikut paling sering muncul ketika pembatalan diterapkan.
- Menelan sinyal pembatalan dengan blok catch yang terlalu luas. Blok
catch (Exception)ikut menangkapOperationCanceledException, sehingga pembatalan yang seharusnya rapi justru dicatat sebagai error. TangkapOperationCanceledExceptionsecara terpisah dan biarkan sinyalnya berhenti di tempat yang benar. - Menerima token tetapi tidak meneruskannya. Token yang hanya disimpan pada parameter tanpa pernah dipakai membuat lapisan dalam tetap berjalan meskipun pemanggil sudah membatalkan, karena hanya API yang menerima token yang dapat ikut dibatalkan.
- Memeriksa token terlalu jarang. Satu pemeriksaan di awal metode tidak cukup untuk pekerjaan panjang, sehingga titik pemeriksaan ditempatkan di antara langkah-langkah besar agar respons terhadap pembatalan tetap wajar.
Ringkasan#
- Pembatalan .NET bersifat kooperatif.
CancellationTokenSourcememegang keputusan melaluiCancel, sedangkanCancellationTokenditeruskan ke metode sebagai pandangan baca yang tidak dapat memaksa apa pun. - Pekerjaan menaati token melalui pemeriksaan eksplisit seperti
ThrowIfCancellationRequestedatau melalui API yang menerima token sepertiTask.Delaydengan argumen token. - Keadaan pembatalan bersifat kekal dan mengalir satu arah, sedangkan token gabungan dari
CreateLinkedTokenSourceterbatal apabila salah satu sumbernya terbatal.
Referensi#
- CancellationToken — Microsoft Learn — Referensi tipe token pembatalan beserta seluruh anggotanya.
- Cancellation in Managed Threads — Microsoft Learn — Panduan pola pembatalan kooperatif antara sumber pembatalan dan kode pekerja.