CancellationToken, CancellationTokenSource'tan neden ayrı?


137

Sınıfa CancellationTokenek olarak .NET struct'ın neden tanıtıldığına dair bir mantık arıyorum CancellationTokenSource. Anlıyorum nasıl API kullanılacaksa, aynı zamanda anlamak istiyorum neden o şekilde olduğunu tasarlanmıştır.

Yani, neden elimizde:

var cts = new CancellationTokenSource();
SomeCancellableOperation(cts.Token);

...
public void SomeCancellableOperation(CancellationToken token) {
    ...
    token.ThrowIfCancellationRequested();
    ...
}

doğrudan CancellationTokenSourceetrafta dolaşmak yerine :

var cts = new CancellationTokenSource();
SomeCancellableOperation(cts);

...
public void SomeCancellableOperation(CancellationTokenSource cts) {
    ...
    cts.ThrowIfCancellationRequested();
    ...
}

Bu, iptal durumu kontrollerinin jetonun etrafından dolaşmaktan daha sık gerçekleştiği gerçeğine dayanan bir performans optimizasyonu mu?

Böylece bu CancellationTokenSourcetakip ve güncelleme yapabilir CancellationTokensve her belirteç için iptal kontrolü yerel bir alan erişimidir?

Her iki durumda da kilitlemesiz geçici bir bool'un yeterli olduğu göz önüne alındığında, bunun neden daha hızlı olacağını hala anlayamıyorum.

Teşekkürler!

Yanıtlar:


110

Bu sınıfların tasarımında ve uygulanmasında görev aldım.

Kısa cevap, " endişelerin ayrılması " dır . Çeşitli uygulama stratejilerinin olduğu ve bazılarının en azından tip sistemi ve ilk öğrenmeye göre daha basit olduğu oldukça doğrudur. Bununla birlikte, CTS ve CT birçok senaryoda (derin kitaplık yığınları, paralel hesaplama, zaman uyumsuz vb.) Kullanılmak üzere tasarlanmıştır ve bu nedenle birçok karmaşık kullanım durumu göz önünde bulundurularak tasarlanmıştır. Performanstan ödün vermeden başarılı kalıpları teşvik etmeyi ve anti-kalıpları caydırmayı amaçlayan bir tasarımdır.

Yanlış davranan API'ler için kapı açık bırakılırsa, iptal tasarımının kullanışlılığı hızla aşınabilir.

CancellationTokenSource == "iptal tetikleyicisi", artı bağlantılı dinleyiciler oluşturur

CancellationToken == "iptal dinleyicisi"


7
Çok teşekkürler! İçerideki bilgiler gerçekten takdir ediliyor.
Andrey Tarantsov

stackoverflow.com/questions/39077497/… StreamSocket'in giriş akışı için varsayılan zaman aşımının ne olması gerektiğine dair bir fikriniz var mı? Okuma işlemini iptal etmek için cancellationtoken kullandığımda ilgili soketi de kapatıyor. Bu sorunun üstesinden gelmenin herhangi bir yolu olabilir mi?
sam18

@Mike - Sadece merak ediyorum: Nasıl olur da bir CT'de olduğu gibi bir CTS'de ThrowIfCancellationRequested () gibi bir şeyi arayamazsınız?
rory.ap

Farklı sorumlulukları vardır ve cts.Token.ThrowIfCancellationRequested () yazmak için çok ayrıntılı değildir, bu nedenle dinleyici API'sini doğrudan CTS'ye eklemedik.
Mike Liddell

@Mike Neden cancellationtoken sınıf değil yapıdır? Herhangi bir performans avantajı göremiyorum
Neir0

85

Tam sorum vardı ve bu tasarımın arkasındaki mantığı anlamak istedim.

Kabul edilen cevap mantığı tam olarak doğru buldu. İşte bu özelliği tasarlayan ekibin onayı (vurgu benim):

Çerçevenin temelini iki yeni tür oluşturur: A CancellationToken , 'potansiyel iptal talebini' temsil eden bir yapıdır. Bu yapı, bir parametre olarak yöntem çağrılarına aktarılır ve yöntem, bunu yoklayabilir veya iptal istendiğinde çalıştırılacak bir geri aramayı kaydedebilir. A CancellationTokenSource, bir iptal talebini başlatmak için mekanizma sağlayan bir sınıftır ve Token ilişkili bir belirteci elde etmek için bir özelliğe sahiptir . Bu iki sınıfı tek bir sınıfta birleştirmek doğal olurdu, ancak bu tasarım iki temel işlemin (iptal talebini başlatma, iptali gözlemleme ve buna yanıt verme) net bir şekilde ayrılmasına izin verir. Özellikle, yalnızca a alan yöntemler CancellationTokenbir iptal talebini gözlemleyebilir ancak bir iptal talebini başlatamaz.

Bağlantı: .NET 4 İptal Çerçevesi

Bana göre CancellationTokendurumu sadece gözlemleyebilen ve değiştiremeyen gerçeği son derece kritik. Jetonu bir şeker gibi dağıtabilir ve sizden başka birinin onu iptal edeceğinden asla endişelenmeyin. Sizi düşmanca üçüncü taraf kodlarından korur. Evet, şans düşük, ama şahsen bu garantiyi seviyorum.

Ayrıca API'yi daha temiz hale getirdiğini ve yanlışlıkla yapılan hataları önlediğini ve daha iyi bileşen tasarımını teşvik ettiğini düşünüyorum.

Bu sınıfların her ikisi için genel API'ye bakalım.

CancellationToken API

CancellationTokenSource API

Bunları birleştirirseniz, LongRunningFunction yazarken, kullanmamam gereken çoklu 'İptal' aşırı yüklemeleri gibi yöntemler göreceğim. Kişisel olarak Dispose yöntemini görmekten de nefret ediyorum.

Mevcut sınıf tasarımının 'başarı çukuru' felsefesini takip ettiğini düşünüyorum, geliştiricilere üstesinden gelebilecek daha iyi bileşenler oluşturmaları için rehberlik ediyor. Task iptal ve ardından bunları karmaşık iş akışları oluşturmak için çeşitli şekillerde bir arada kullanmaları için yönlendiriyor.

Size bir soru sorayım, belirtecin amacının ne olduğunu merak ettiniz mi? Bana mantıklı gelmedi. Ve sonra Yönetilen İş Parçacıklarında İptali okudum ve her şey kristal netliğe kavuştu.

TPL'deki İptal Çerçeve Tasarımının kesinlikle birinci sınıf olduğuna inanıyorum.


2
CancellationTokenSourceİptal isteğinin ilişkili belirteci üzerinde nasıl gerçekten başlatılabileceğini merak ediyorsanız (belirteç bunu kendi başına yapamaz): CancellationToken bu dahili kurucuya sahiptir: internal CancellationToken(CancellationTokenSource source) { this.m_source = source; }ve bu özellik: public bool IsCancellationRequested { get { return this.m_source != null && this.m_source.IsCancellationRequested; } }CancellationTokenSource dahili kurucuyu kullanır, bu nedenle belirteç, kaynak (m_source)
chviLadislav

65

Teknik nedenlerle değil, anlamsal nedenlerle ayrıdırlar. CancellationTokenILSpy altında uygulamasına bakarsanız, bunun yalnızca bir sarmalayıcı olduğunu göreceksiniz CancellationTokenSource(ve dolayısıyla performans açısından bir referansın etrafından dolaşmaktan farklı olmadığını).

İşleri daha öngörülebilir hale getirmek için bu işlevsellik ayrımını sağlarlar: a yöntemini geçtiğinizde CancellationToken, onu iptal edebilecek tek kişinin siz olduğunuzu bilirsiniz. Elbette, yöntem hala a atabilir TaskCancelledException, ancak CancellationTokenkendisi - ve aynı belirteci referans alan diğer yöntemler - güvenli kalacaktır.


Teşekkürler! Göz kırpma tepkim, anlamsal olarak, IReadOnlyList'i sağlamakla aynı derecede tartışmalı bir yaklaşım olmasıdır. Yine de kulağa çok mantıklı geliyor, bu yüzden cevabınızı kabul ediyorum.
Andrey Tarantsov

1
Daha fazla düşündüğümde, kendimi inandırıcılığı sorgularken buluyorum. O halde CancellationTokenSource'un uygulayacağı ICancellationToken arayüzünü sağlamak daha mantıklı olmaz mıydı? Keşke .NET ekibinden biri
devreye girse

Bu yine de kurnaz bir programcının yayınlamasına neden olabilir CancellationTokenSource. Sadece "bunu yapma" diyebileceğini düşünürdün, ama insanlar (ben de dahil!) Bazı gizli işlevlere ulaşmak için bu şeyleri ara sıra yaparlar ve bu olur. En azından şu anki teorim bu.
Cory Nelson

1
Güvenlikle ilgili olmadığı sürece çoğu durumda API'leri böyle tasarlamazsınız. "Gelecekte performans optimizasyonları için seçeneklerini açık tutma" gibi başka bir açıklamaya bahse girmeyi tercih ederim. Ama yine de seninki şimdiye kadarki en iyisi.
Andrey Tarantsov

Hey, umarım kabul edilen cevabı uygulamaya dahil olan kişiye yeniden atamama izin verirsiniz. İkiniz de aynı şeyi söylerken, kesin cevabın üstte olmasından SO'nun yararlandığını düşünüyorum.
Andrey Tarantsov

10

CancellationToken yapı, yöntemlere aktarılması nedeniyle çok sayıda kopya bulunabilecek bir yapıdır.

Kaynak CancellationTokenSourceçağrılırken bir belirtecin TÜM kopyalarının durumunu ayarlar Cancel. Bu MSDN sayfasına bakın

Tasarımın nedeni sadece endişelerin ayrılması ve bir yapının hızı olabilir.


1
Teşekkürler. Başka bir yanıtın teknik olarak (IL'de) CancellationTokenSource'un belirteçlerin durumunu ayarlamadığını söylediğine dikkat edin; bunun yerine belirteçler, CancellationTokenSource'a gerçek bir referansı sarar ve iptali kontrol etmek için ona erişir. Bir şey varsa, hız sadece burada kaybedilebilir.
Andrey Tarantsov

+1. Ben anlamıyorum bir değer türü ise, bu nedenle her yöntemin kendi değeri vardır (ayrı bir değer). peki bir yöntem iptalin çağrılıp çağrılmadığını nasıl anlar? TokenSource'a herhangi bir referansı YOKTUR. tüm metodlar "5" gibi yerel bir değer türüdür. açıklayabilir misin ?
Royi Namir

1
@RoyiNamir: Her CancellationToken, CancellationTokenSource türünde "m_source" özel referansına sahiptir
Andreyul

2

Her CancellationTokenSourcene sebeple olursa olsun, iptali düzenleyen 'şey'. Bu iptali verdiği tüm 'lere' göndermenin 'bir yoluna ihtiyacı CancellationTokenvar. Örneğin ASP.NET, bir istek iptal edildiğinde işlemleri iptal edebilir. Her talep, CancellationTokenSourceiptali düzenlediği tüm belirteçlere ileten bir özelliğe sahiptir.

BTW birim testi için bu harika - kendi iptal jetonu kaynağınızı oluşturun, bir jeton Cancelalın, kaynağı arayın ve jetonu iptali işlemesi gereken kodunuza iletin.


Teşekkürler - ama her iki sorumluluk da ( birbiriyle çok ilişkili) aynı sınıfa verilebilir. Neden ayrı olduklarını gerçekten açıklamıyor.
Andrey Tarantsov
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.