CancellationToken için .NET Programcı Kılavuzu
Yayınlanan: 2022-08-23Bazen 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ı |
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:
- Bir
RandomWorker
sınıfı oluşturun. -
RandomWorker
, bazı rastgele işleri yürüten birDoWorkAsync
yöntemine sahip olmalıdır. -
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.
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