CancellationToken için .NET Programcı Kılavuzu

Yayınlanan: 2022-08-23

Bazen iptal etmek iyi bir şeydir. .NET projelerimin çoğunda hem iç hem de dış süreçleri iptal etmek için bolca motivasyonum oldu. Microsoft, geliştiricilerin çeşitli karmaşık uygulamalarda bu yaygın kullanım durumuna yaklaştıklarını öğrendi ve daha iyi bir yol olması gerektiğine karar verdi. Böylece, alt düzey çoklu iş parçacığı ve süreçler arası iletişim yapıları kullanılarak oluşturulan CancellationToken olarak ortak bir iptal iletişim modeli tanıtıldı. Bu modelle ilgili ilk araştırmamın bir parçası olarak ve Microsoft'un uygulanması için gerçek .NET kaynak kodunu inceledikten sonra, CancellationToken çok daha geniş bir dizi sorunu çözebildiğini keşfettim: uygulamaların çalışma durumlarındaki abonelikler, farklı kullanan işlemlerin zaman aşımına uğraması. tetikleyiciler ve bayraklar aracılığıyla genel süreçler arası iletişim.

Amaçlanan CancellationToken Kullanım Örneği

CancellationToken , .NET 4'te, iptal işlemlerine yönelik mevcut çözümleri geliştirmenin ve standartlaştırmanın bir yolu olarak tanıtıldı. Popüler programlama dillerinin uygulama eğiliminde olduğu iptali ele almak için dört genel yaklaşım vardır:

Öldürmek Söyle, hayırı cevap olarak kabul etme Kibarca sorun ve reddedilmeyi kabul edin Bayrağı kibarca ayarlayın, isterse oylamasına izin verin
Yaklaşmak Sert duruş; tutarsızlıkları daha sonra çöz Durmasını söyle ama işleri temizlemesine izin ver Durdurmak için doğrudan ama nazik bir istek Durmasını isteyin ama zorlamayın
Özet Yolsuzluk ve acıya giden kesin bir yol Temiz durma noktalarına izin verir ancak durması gerekir Temiz durma noktalarına izin verir, ancak iptal talebi göz ardı edilebilir İptal bir bayrak aracılığıyla isteniyor
Pthread'ler pthread_kill ,
pthread_cancel (zaman uyumsuz)
pthread_cancel (ertelenmiş mod) n/a Bir bayrak aracılığıyla
.AĞ Thread.Abort n/a Thread.Interrupt CancellationToken bir bayrak aracılığıyla
Java Thread.destroy ,
Thread.stop
n/a Thread.interrupt Bir bayrak veya Thread.interrupted aracılığıyla
piton PyThreadState_SetAsyncExc n/a asyncio.Task.cancel Bir bayrak aracılığıyla
Rehberlik Kabul edilemez; bu yaklaşımdan kaçının Kabul edilebilir, özellikle bir dil istisnaları veya çözülmeyi desteklemediğinde Dil destekliyorsa kabul edilebilir Daha iyi, ama daha çok grup çalışması
İptal Yaklaşım Özeti ve Dil Örnekleri

CancellationToken , iptal konuşmasının işbirlikçi olduğu son kategoride bulunur.

Microsoft, CancellationToken tanıttıktan sonra, özellikle birçok büyük .NET API'si bu belirteçleri yerel olarak kullanacak şekilde güncellendiğinden, geliştirme topluluğu bunu hızla benimsedi. Örneğin, ASP.NET Core 2.0'dan başlayarak, eylemler, bir HTTP isteğinin kapatılıp kapatılmadığını bildiren isteğe bağlı bir CancellationToken parametresini destekleyerek herhangi bir işlemin iptal edilmesini sağlar ve böylece kaynakların gereksiz kullanımından kaçınılır.

.NET kod tabanına derinlemesine bir dalıştan sonra, CancellationToken kullanımının iptal ile sınırlı olmadığı anlaşıldı.

Mikroskop Altında CancellationToken

CancellationToken uygulamasına daha yakından baktığımızda, bunun sadece basit bir bayrak (yani ManualResetEvent ) ve bu bayrağı izleme ve değiştirme yeteneği sağlayan destekleyici altyapı olduğunu görüyoruz. CancellationToken 'ın ana yardımcı programı adındadır, bu da işlemleri iptal etmenin yaygın yolunun bu olduğunu gösterir. Günümüzde, zaman uyumsuz veya uzun süreli işlemlere sahip herhangi bir .NET kitaplığı, paketi veya çerçevesi, bu belirteçler aracılığıyla iptale izin verir.

CancellationToken , bayrağını manuel olarak "true" olarak ayarlayarak veya belirli bir zaman aralığı geçtikten sonra "true" olarak değiştirmek üzere programlayarak tetiklenebilir. CancellationToken nasıl tetiklendiğinden bağımsız olarak, bu belirteci izleyen istemci kodu, belirteç bayrağının değerini üç yöntemden biri aracılığıyla belirleyebilir:

  • WaitHandle kullanma
  • CancellationToken bayrağını yoklama
  • Bayrağın durumu programlı bir abonelik yoluyla güncellendiğinde müşteri kodunu bilgilendirme

.NET kod tabanında daha fazla araştırma yaptıktan sonra, .NET ekibinin CancellationTokens iptalle bağlantılı olmayan diğer senaryolarda yararlı bulduğu ortaya çıktı. Karmaşık durumları basitleştirmek için C# geliştiricilerini çok iş parçacıklı ve süreçler arası koordinasyonla güçlendiren bu gelişmiş ve marka dışı kullanım örneklerinden bazılarını inceleyelim.

Gelişmiş Etkinlikler için CancellationTokens

ASP.NET Core uygulamaları yazarken bazen uygulamamızın ne zaman başladığını bilmemiz gerekiyor ya da ana bilgisayar kapatma işlemine kodumuzu enjekte etmemiz gerekiyor. Bu durumlarda, IHostApplicationLifetime arabirimini kullanırız (önceden IApplicationLifetime ). Bu arabirim (.NET Core deposundan), üç ana olayı iletmek için CancellationToken kullanır: ApplicationStarted , ApplicationStopping ve 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(); } }

İlk bakışta CancellationToken s buraya ait değilmiş gibi görünebilir, özellikle de olay olarak kullanıldıklarından. Bununla birlikte, daha fazla inceleme, bu belirteçlerin mükemmel bir uyum olduğunu ortaya koymaktadır:

  • Esnektirler ve arabirim istemcisinin bu olayları dinlemesi için birden fazla yol sağlar.
  • Kutunun dışında iplik güvenlidir.
  • CancellationToken 'leri birleştirerek farklı kaynaklardan oluşturulabilirler.

CancellationToken 'ler her olay ihtiyacı için mükemmel olmasa da, uygulamanın başlatılması veya durdurulması gibi yalnızca bir kez gerçekleşen olaylar için idealdir.

Zaman Aşımı için CancellationToken

Varsayılan olarak, ASP.NET bize kapatmamız için çok az zaman verir. Biraz daha zaman istediğimiz durumlarda, yerleşik HostOptions sınıfını kullanmak bu zaman aşımı değerini değiştirmemize izin verir. Altında, bu zaman aşımı değeri bir CancellationToken sarılır ve alttaki alt süreçlere beslenir.

IHostedService StopAsync yöntemi bu kullanıma harika bir örnektir:

 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); } }

IHostedService arabirim tanımında görüldüğü gibi, StopAsync yöntemi bir CancellationToken parametresi alır. Bu parametreyle ilişkili yorum, Microsoft'un CancellationToken için ilk amacının bir iptal işleminden ziyade bir zaman aşımı mekanizması olduğunu açıkça belirtir.

Benim düşünceme göre, bu arayüz CancellationToken varlığından önce mevcut olsaydı, bu bir TimeSpan parametresi olabilirdi - durdurma işleminin ne kadar süreyle işlemesine izin verildiğini belirtmek için. Tecrübelerime göre, zaman aşımı senaryoları neredeyse her zaman harika bir ek yardımcı CancellationToken dönüştürülebilir.

Şimdilik StopAsync metodunun nasıl tasarlandığını bildiğimizi unutalım ve bunun yerine bu metodun sözleşmesini nasıl tasarlayacağımızı düşünelim. Önce gereksinimleri tanımlayalım:

  • StopAsync yöntemi, hizmeti durdurmaya çalışmalıdır.
  • StopAsync yöntemi, zarif bir durdurma durumuna sahip olmalıdır.
  • Kesintisiz bir durdurma durumuna ulaşılıp ulaşılmadığına bakılmaksızın, barındırılan bir hizmetin, zaman aşımı parametremiz tarafından tanımlandığı gibi, durdurulması için bir maksimum süreye sahip olması gerekir.

Herhangi bir biçimde bir StopAsync yöntemine sahip olarak ilk gereksinimi karşılarız. Kalan gereksinimler zor. CancellationToken , konuşmayı güçlendirmek için standart bir .NET bayrak tabanlı iletişim aracı kullanarak bu gereksinimleri tam olarak karşılar.

Bir Bildirim Mekanizması Olarak CancellationToken

CancellationToken arkasındaki en büyük sır, bunun sadece bir bayrak olmasıdır. CancellationToken işlemleri durdurmak yerine başlatmak için nasıl kullanılabileceğini gösterelim.

Aşağıdakileri göz önünde bulundur:

  1. Bir RandomWorker sınıfı oluşturun.
  2. RandomWorker , bazı rastgele işleri yürüten bir DoWorkAsync yöntemine sahip olmalıdır.
  3. DoWorkAsync yöntemi, arayanın işin ne zaman başlayacağını belirtmesine izin vermelidir.
 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); } } }

Yukarıdaki sınıf, ilk iki gereksinimi karşılar ve bizi üçüncüsü ile baş başa bırakır. Çalışanımızı tetiklemek için kullanabileceğimiz bir zaman aralığı veya basit bir bayrak gibi birkaç alternatif arayüz vardır:

 # With a time span Task DoWorkAsync(TimeSpan startAfter); # Or a simple flag bool ShouldStart { get; set; } Task DoWorkAsync();

Bu iki yaklaşım iyidir, ancak hiçbir şey CancellationToken kullanmak kadar zarif değildir:

 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); } } }

Bu örnek istemci kodu, bu tasarımın gücünü gösterir:

 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 CancellationToken ve ilgili tüm süreçlerin tetiklenmesini koordine edecektir. Bu durumda ilgili süreç, başlamayı bekleyen RandomWorker . Bu yaklaşım, varsayılan CancellationToken uygulamasında oluşturulan iş parçacığı güvenliğinden yararlanmamızı sağlar.

Genişletilmiş İptal Simgesi Araç Kutusu

Bu örnekler, CancellationToken amaçlanan kullanım durumunun dışında yararlı olan bir çözüm araç kutusunu nasıl sağladığını göstermektedir. Araçlar, süreçler arası bayrak tabanlı iletişimi içeren birçok senaryoda kullanışlı olabilir. Zaman aşımları, bildirimler veya tek seferlik olaylarla karşı karşıya kalsak da, Microsoft tarafından test edilmiş bu zarif uygulamaya geri dönebiliriz.

Yukarıdan aşağıya "Altın" (altın renkli), "Microsoft" ve "Partner" (her ikisi de siyah) sözcükleri ve ardından Microsoft logosu görünür.
Bir Microsoft Altın İş Ortağı olarak Toptal, Microsoft uzmanlarından oluşan seçkin ağınızdır. İhtiyacınız olan uzmanlarla her yerde ve tam olarak ihtiyaç duyduğunuz anda yüksek performanslı ekipler oluşturun!

Toptal Mühendislik Blogunda Daha Fazla Okuma:

  • .NET Core: Vahşileşmek ve Açık Kaynak. Microsoft, neden bu kadar uzun sürdü?!
  • ASP.NET Core ile ASP.NET Web API Oluşturma
  • .NET Projeleri Nasıl Önyüklenir ve Oluşturulur