Panduan Pemrogram .NET untuk CancellationToken
Diterbitkan: 2022-08-23Terkadang membatalkan adalah hal yang baik. Dalam banyak proyek .NET saya, saya memiliki banyak motivasi untuk membatalkan proses internal dan eksternal. Microsoft mengetahui bahwa pengembang mendekati kasus penggunaan umum ini dalam berbagai implementasi kompleks dan memutuskan harus ada cara yang lebih baik. Dengan demikian, pola komunikasi pembatalan yang umum diperkenalkan sebagai CancellationToken
, yang dibangun menggunakan konstruksi multithreading tingkat rendah dan komunikasi antarproses. Sebagai bagian dari penelitian awal saya tentang pola ini—dan setelah menggali kode sumber .NET aktual untuk implementasi Microsoft—saya menemukan bahwa CancellationToken
dapat memecahkan serangkaian masalah yang jauh lebih luas: langganan pada status menjalankan aplikasi, waktu keluar operasi menggunakan berbagai pemicu, dan komunikasi antarproses umum melalui flag.
Kasus Penggunaan Token Pembatalan yang Dimaksudkan
CancellationToken
diperkenalkan di .NET 4 sebagai sarana untuk meningkatkan dan menstandarisasi solusi yang ada untuk membatalkan operasi. Ada empat pendekatan umum untuk menangani pembatalan yang cenderung diterapkan oleh bahasa pemrograman populer:
Membunuh | Katakan, jangan menerima jawaban tidak | Mintalah dengan sopan, dan terima penolakan | Atur bendera dengan sopan, biarkan polling jika mau | |
---|---|---|---|---|
Mendekati | Berhenti keras; menyelesaikan inkonsistensi nanti | Katakan padanya untuk berhenti tetapi biarkan dia membersihkan semuanya | Permintaan langsung tapi lembut untuk berhenti | Minta berhenti, tapi jangan dipaksa |
Ringkasan | Jalan pasti menuju korupsi dan rasa sakit | Memungkinkan titik berhenti yang bersih tetapi harus berhenti | Memungkinkan titik pemberhentian yang bersih, tetapi permintaan pembatalan dapat diabaikan | Pembatalan diminta melalui bendera |
Pthreads | pthread_kill ,pthread_cancel (async) | pthread_cancel (mode ditangguhkan) | tidak ada | Melalui sebuah bendera |
.BERSIH | Thread.Abort | tidak ada | Thread.Interrupt | Melalui bendera di CancellationToken |
Jawa | Thread.destroy ,Thread.stop | tidak ada | Thread.interrupt | Melalui bendera atau Thread.interrupted |
Python | PyThreadState_SetAsyncExc | tidak ada | asyncio.Task.cancel | Melalui sebuah bendera |
Panduan | tidak dapat diterima; hindari pendekatan ini | Dapat diterima, terutama ketika bahasa tidak mendukung pengecualian atau pelepasan | Dapat diterima jika bahasa mendukungnya | Lebih baik, tetapi lebih merupakan upaya kelompok |
CancellationToken
berada di kategori terakhir, di mana percakapan pembatalan bersifat kooperatif.
Setelah Microsoft memperkenalkan CancellationToken
, komunitas pengembangan dengan cepat menerimanya, terutama karena banyak .NET API utama diperbarui untuk menggunakan token ini secara asli. Misalnya, dimulai dengan ASP.NET Core 2.0, tindakan mendukung parameter CancellationToken
opsional yang mungkin memberi sinyal jika permintaan HTTP telah ditutup, memungkinkan pembatalan operasi apa pun dan dengan demikian menghindari penggunaan sumber daya yang tidak perlu.
Setelah menyelam jauh ke dalam basis kode .NET, menjadi jelas bahwa penggunaan CancellationToken
tidak terbatas pada pembatalan.
PembatalanToken Di Bawah Mikroskop
Saat melihat lebih dekat pada implementasi CancellationToken
, kami melihat itu hanya sebuah flag sederhana (yaitu, ManualResetEvent
) dan infrastruktur pendukung yang menyediakan kemampuan untuk memantau dan mengubah flag tersebut. Utilitas utama CancellationToken
ada dalam namanya, yang menunjukkan ini adalah cara umum untuk membatalkan operasi. Saat ini, perpustakaan, paket, atau kerangka kerja .NET apa pun dengan operasi asinkron atau yang berjalan lama memungkinkan pembatalan melalui token ini.
CancellationToken
dapat dipicu baik dengan menyetel flag-nya secara manual ke "true" atau memprogramnya untuk berubah menjadi "true" setelah rentang waktu tertentu berlalu. Terlepas dari bagaimana CancellationToken
dipicu, kode klien yang memantau token ini dapat menentukan nilai tanda tanda melalui salah satu dari tiga metode:
- Menggunakan
WaitHandle
- Polling bendera
CancellationToken
- Memberi tahu kode klien saat status bendera diperbarui melalui langganan terprogram
Setelah penelitian lebih lanjut dalam basis kode .NET, menjadi jelas bahwa tim .NET menemukan CancellationTokens
berguna dalam skenario lain yang tidak terkait dengan pembatalan. Mari kita jelajahi beberapa kasus penggunaan tingkat lanjut dan di luar merek ini, yang memberdayakan pengembang C# dengan koordinasi multithread dan antarproses untuk menyederhanakan situasi yang kompleks.
Token Pembatalan untuk Acara Lanjutan
Saat menulis aplikasi ASP.NET Core, terkadang kita perlu mengetahui kapan aplikasi kita telah dimulai, atau kita perlu memasukkan kode kita ke dalam proses shutdown host. Dalam kasus tersebut, kami menggunakan antarmuka IHostApplicationLifetime
(sebelumnya IApplicationLifetime
). Antarmuka ini (dari repositori .NET Core) menggunakan CancellationToken
untuk mengomunikasikan tiga peristiwa utama: ApplicationStarted
, ApplicationStopping
, dan ApplicationStopped
:
namespace Microsoft.Extensions.Hosting { /// <summary> /// Allows consumers to be notified of application lifetime events. /// This interface is not intended to be user-replaceable. /// </summary> public interface IHostApplicationLifetime { /// <summary> /// Triggered when the application host has fully started. /// </summary> CancellationToken ApplicationStarted { get; } /// <summary> /// Triggered when the application host is starting a graceful shutdown. /// Shutdown will block until all callbacks registered on /// this token have completed. /// </summary> CancellationToken ApplicationStopping { get; } /// <summary> /// Triggered when the application host has completed a graceful shutdown. /// The application will not exit until all callbacks registered on /// this token have completed. /// </summary> CancellationToken ApplicationStopped { get; } /// <summary> /// Requests termination of the current application. /// </summary> void StopApplication(); } }
Sepintas, sepertinya CancellationToken
s tidak pantas di sini, terutama karena mereka digunakan sebagai event. Namun, pemeriksaan lebih lanjut mengungkapkan token ini sangat cocok:
- Mereka fleksibel, memungkinkan berbagai cara bagi klien antarmuka untuk mendengarkan acara ini.
- Mereka aman dari kotak.
- Mereka dapat dibuat dari sumber yang berbeda dengan menggabungkan
CancellationToken
s.
Meskipun CancellationToken
s tidak sempurna untuk setiap kebutuhan acara, mereka ideal untuk acara yang hanya terjadi sekali, seperti aplikasi mulai atau berhenti.
CancellationToken untuk Timeout
Secara default, ASP.NET memberi kita sedikit waktu untuk mematikan. Dalam kasus di mana kita menginginkan sedikit lebih banyak waktu, menggunakan kelas HostOptions
memungkinkan kita untuk mengubah nilai batas waktu ini. Di bawahnya, nilai batas waktu ini dibungkus dalam CancellationToken
dan dimasukkan ke dalam subproses yang mendasarinya.
Metode IHostedService
StopAsync
adalah contoh yang bagus dari penggunaan ini:
namespace Microsoft.Extensions.Hosting { /// <summary> /// Defines methods for objects that are managed by the host. /// </summary> public interface IHostedService { /// <summary> /// Triggered when the application host is ready to start the service. /// </summary> /// <param name="cancellationToken">Indicates that the start /// process has been aborted.</param> Task StartAsync(CancellationToken cancellationToken); /// <summary> /// Triggered when the application host is performing a graceful shutdown. /// </summary> /// <param name="cancellationToken">Indicates that the shutdown /// process should no longer be graceful.</param> Task StopAsync(CancellationToken cancellationToken); } }
Seperti yang terlihat dalam definisi antarmuka IHostedService
, metode StopAsync
mengambil satu parameter CancellationToken
. Komentar yang terkait dengan parameter tersebut dengan jelas mengomunikasikan maksud awal Microsoft untuk CancellationToken
adalah sebagai mekanisme batas waktu, bukan sebagai proses pembatalan.
Menurut pendapat saya, jika antarmuka ini sudah ada sebelum keberadaan CancellationToken
, ini bisa menjadi parameter TimeSpan
—untuk menunjukkan berapa lama operasi penghentian diizinkan untuk diproses. Dalam pengalaman saya, skenario batas waktu hampir selalu dapat dikonversi ke CancellationToken
dengan utilitas tambahan yang hebat.
Untuk saat ini, mari kita lupakan bahwa kita tahu bagaimana metode StopAsync
dirancang dan alih-alih berpikir tentang bagaimana kita akan merancang kontrak metode ini. Pertama mari kita tentukan persyaratannya:
- Metode
StopAsync
harus mencoba menghentikan layanan. - Metode
StopAsync
harus memiliki status berhenti yang anggun. - Terlepas dari apakah status berhenti anggun tercapai, layanan yang dihosting harus memiliki waktu maksimum untuk berhenti, seperti yang ditentukan oleh parameter batas waktu kami.
Dengan memiliki metode StopAsync
dalam bentuk apa pun, kami memenuhi persyaratan pertama. Persyaratan yang tersisa rumit. CancellationToken
memenuhi persyaratan ini secara tepat dengan menggunakan alat komunikasi berbasis flag .NET standar untuk memberdayakan percakapan.
CancellationToken Sebagai Mekanisme Notifikasi
Rahasia terbesar di balik CancellationToken
adalah bahwa itu hanya sebuah bendera. Mari kita ilustrasikan bagaimana CancellationToken
dapat digunakan untuk memulai proses alih-alih menghentikannya.
Pertimbangkan hal berikut:
- Buat kelas
RandomWorker
. -
RandomWorker
harus memiliki metodeDoWorkAsync
yang menjalankan beberapa pekerjaan acak. - Metode
DoWorkAsync
harus mengizinkan pemanggil untuk menentukan kapan pekerjaan harus dimulai.
public class RandomWorker { public RandomWorker(int id) { Id = id; } public int Id { get; } public async Task DoWorkAsync() { for (int i = 1; i <= 10; i++) { Console.WriteLine($"[Worker {Id}] Iteration {i}"); await Task.Delay(1000); } } }
Kelas di atas memenuhi dua persyaratan pertama, meninggalkan kami dengan yang ketiga. Ada beberapa antarmuka alternatif yang dapat kita gunakan untuk memicu pekerja kita, seperti rentang waktu atau tanda sederhana:
# With a time span Task DoWorkAsync(TimeSpan startAfter); # Or a simple flag bool ShouldStart { get; set; } Task DoWorkAsync();
Kedua pendekatan ini baik-baik saja, tetapi tidak ada yang seanggun menggunakan CancellationToken
:
public class RandomWorker { public RandomWorker(int id) { Id = id; } public int Id { get; } public async Task DoWorkAsync(CancellationToken startToken) { startToken.WaitHandle.WaitOne(); for (int i = 1; i <= 10; i++) { Console.WriteLine($"[Worker {Id}] Iteration {i}"); await Task.Delay(1000); } } }
Contoh kode klien ini menggambarkan kekuatan desain ini:
using System; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace CancelToStart { public class Program { static void Main(string[] args) { CancellationTokenSource startCts = new CancellationTokenSource(); startCts.CancelAfter(TimeSpan.FromSeconds(10)); var tasks = Enumerable.Range(0, 10) .Select(i => new RandomWorker(i)) .Select(worker => worker.DoWorkAsync(startCts.Token)) .ToArray(); Task.WaitAll(tasks, CancellationToken.None); } } }
CancellationTokenSource
akan membuat CancellationToken
kami di belakang layar dan mengoordinasikan pemicuan semua proses terkait. Dalam hal ini, proses terkait adalah RandomWorker
kami, yang menunggu untuk dimulai. Pendekatan ini memungkinkan kami untuk memanfaatkan keamanan utas yang dimasukkan ke dalam implementasi CancellationToken
default.
Kotak Alat Token Pembatalan yang Luas
Contoh-contoh ini menunjukkan bagaimana CancellationToken
menyediakan kotak alat solusi yang berguna di luar kasus penggunaan yang dimaksudkan. Alat ini dapat berguna dalam banyak skenario yang melibatkan komunikasi berbasis flag antarproses. Apakah kita dihadapkan dengan batas waktu, pemberitahuan, atau peristiwa satu kali, kita dapat kembali pada implementasi elegan yang telah diuji oleh Microsoft ini.
Bacaan Lebih Lanjut di Blog Teknik Toptal:
- .NET Core: Menjadi Liar dan Sumber Terbuka. Microsoft, apa yang membuatmu begitu lama?!
- Membangun ASP.NET Web API Dengan ASP.NET Core
- Cara Boot-strap dan Membuat Proyek .NET