Perché CancelToken è separato da CancelTokenSource?


137

Sto cercando una logica del perché .NET CancellationTokenstruct è stato introdotto oltre alla CancellationTokenSourceclasse. Capisco come utilizzare l'API, ma voglio anche capire perché è stato progettato in questo modo.

Cioè, perché abbiamo:

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

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

invece di passare direttamente in CancellationTokenSourcegiro come:

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

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

Si tratta di un'ottimizzazione delle prestazioni basata sul fatto che i controlli dello stato di annullamento si verificano più frequentemente rispetto al passaggio del token?

In modo che CancellationTokenSourcepossa tenere traccia e aggiornareCancellationTokens e per ogni token il controllo di annullamento è un accesso sul campo locale?

Dato che un bool volatile senza blocco è sufficiente in entrambi i casi, non riesco ancora a capire perché sarebbe più veloce.

Grazie!

Risposte:


110

Sono stato coinvolto nella progettazione e realizzazione di queste lezioni.

La risposta breve è " separazione delle preoccupazioni ". È vero che esistono varie strategie di implementazione e che alcune sono più semplici almeno per quanto riguarda il sistema dei tipi e l'apprendimento iniziale. Tuttavia, CTS e CT sono destinati all'uso in moltissimi scenari (come stack di librerie profonde, calcolo parallelo, asincrono, ecc.) E quindi sono stati progettati tenendo presenti molti casi d'uso complessi. È un progetto destinato a incoraggiare modelli di successo e a scoraggiare gli anti-schemi senza sacrificare le prestazioni.

Se la porta fosse lasciata aperta per le API che si comportavano male, l'utilità del progetto di annullamento potrebbe essere rapidamente erosa.

CancellationTokenSource == "trigger di annullamento", oltre a generare ascoltatori collegati

CancellationToken == "listener di annullamento"


7
Molte grazie! La conoscenza interiore è molto apprezzata.
Andrey Tarantsov,

stackoverflow.com/questions/39077497/… Hai idea di cosa dovrebbe essere il timeout predefinito per inputstream di StreamSocket? Quando utilizzo il metodo di cancellazione per annullare l'operazione di lettura, chiude anche il socket interessato. Può esserci un modo per superare questo problema?
sam18,

@Mike - Solo curioso: come mai non puoi chiamare qualcosa come ThrowIfCancellationRequested () su un CTS come puoi su un CT?
rory.ap,

Hanno responsabilità diverse e non è troppo prolisso per scrivere cts.Token.ThrowIfCancellationRequested (), quindi non abbiamo aggiunto l'API listener direttamente su CTS.
Mike Liddell,

@Mike Perché cancellazionetoken è una struttura e non una classe? Non vedo alcun vantaggio in termini di prestazioni
Neir0

85

Avevo la domanda esatta e volevo capire la logica alla base di questo progetto.

La risposta accettata ha ottenuto la logica esattamente corretta. Ecco la conferma del team che ha progettato questa funzione (enfasi sulla mia):

Due nuovi tipi formano la base del framework: A CancellationTokenè una struttura che rappresenta una "potenziale richiesta di cancellazione". Questa struttura viene passata nelle chiamate al metodo come parametro e il metodo può eseguire il polling su di esso o registrare un callback da attivare quando viene richiesta la cancellazione. A CancellationTokenSourceè una classe che fornisce il meccanismo per avviare una richiesta di annullamento e ha una Token proprietà per ottenere un token associato. Sarebbe stato naturale combinare queste due classi in una, ma questo progetto consente di separare in modo netto le due operazioni chiave (avvio di una richiesta di annullamento rispetto all'osservazione e risposta alla cancellazione). In particolare, i metodi che accettano solo a CancellationTokenpossono osservare una richiesta di annullamento ma non possono avviarne una.

Link: .NET 4 Framework di annullamento

Secondo me, il fatto che CancellationTokenpossa solo osservare lo stato e non cambiarlo, è estremamente critico. Puoi distribuire il token come una caramella e non preoccuparti mai che qualcun altro, diverso da te, lo annulli. Ti protegge dal codice di terze parti ostile. Sì, le possibilità sono scarse, ma personalmente mi piace quella garanzia.

Sento anche che rende l'API più pulita, evita errori accidentali e promuove una migliore progettazione dei componenti.

Diamo un'occhiata all'API pubblica per entrambe queste classi.

API annullata

API CancelTokenSource

Se dovessi combinarli, quando scrivo LongRunningFunction, vedrò metodi come quei sovraccarichi multipli di 'Annulla' che non dovrei usare. Personalmente, odio vedere anche il metodo Dispose.

Penso che l'attuale design della classe segua la filosofia della "fossa del successo", guida gli sviluppatori a creare componenti migliori in grado di gestire la Taskcancellazione e quindi a strumentarli insieme in numerosi modi per creare flussi di lavoro complicati.

Lascia che ti faccia una domanda, ti sei mai chiesto qual è lo scopo del token. Non aveva senso per me. E poi ho letto Annullamento in Discussioni gestite e tutto è diventato cristallino.

Credo che il disegno del Framework di annullamento in TPL sia assolutamente di prim'ordine.


2
Se ti chiedi come CancellationTokenSourcepuò effettivamente avviare la richiesta di annullamento sul suo token associato (il token non può farlo da solo): Il modulo di cancellazione ha questo costruttore interno: internal CancellationToken(CancellationTokenSource source) { this.m_source = source; }e questa proprietà: public bool IsCancellationRequested { get { return this.m_source != null && this.m_source.IsCancellationRequested; } }La cancellazioneTokenSource utilizza il costruttore interno, quindi il token fa riferimento al fonte (m_source)
chviLadislav

65

Sono separati non per motivi tecnici ma semantici. Se osservi l'implementazione di CancellationTokensotto ILSpy, scoprirai che è semplicemente un wrapper CancellationTokenSource(e quindi non è diverso dal punto di vista delle prestazioni rispetto al passare un riferimento).

Forniscono questa separazione di funzionalità per rendere le cose più prevedibili: quando passi un metodo a CancellationToken, sai di essere ancora l'unico che può annullarlo. Certo, il metodo potrebbe ancora lanciare un TaskCancelledException, ma lo CancellationTokenstesso - e qualsiasi altro metodo che fa riferimento allo stesso token - rimarrebbe al sicuro.


Grazie! La mia reazione lampo è che, semanticamente, è un approccio discutibile, alla pari di fornire IReadOnlyList. Sembra molto plausibile, però, quindi accetta la tua risposta.
Andrey Tarantsov,

1
Ripensandoci, mi ritrovo a mettere in discussione la plausibilità. Non avrebbe più senso, quindi, fornire l'interfaccia ICancellationToken implementata da CancelTokenSource? Vorrei che qualcuno del team .NET
entrasse

Ciò potrebbe comunque tradursi in un abile programmatore che lancia CancellationTokenSource. Penseresti di poter semplicemente dire "non farlo", ma le persone (incluso me!) Ogni tanto fanno queste cose comunque per ottenere alcune funzionalità nascoste, e succederebbe. Questa è la mia teoria attuale, almeno.
Cory Nelson,

1
Nella maggior parte dei casi non è così che si progettano le API, a meno che non si tratti di sicurezza. Preferirei scommettere su qualche altra spiegazione, come "mantenere le loro opzioni aperte per l'ottimizzazione delle prestazioni in futuro". Tuttavia, il tuo è il migliore finora.
Andrey Tarantsov,

Ehi, spero che mi scuserai di riassegnare la risposta accettata alla persona coinvolta nell'implementazione. Mentre entrambi dite la stessa cosa, penso che SO tragga vantaggio dal fatto che la risposta definitiva sia in cima.
Andrey Tarantsov,

10

La CancellationTokenè una struttura così potrebbero esistere molte copie causa passando lungo ai metodi.

I CancellationTokenSourceImposta lo stato di tutte le copie di un gettone quando chiamata Cancelsulla fonte. Vedi questa pagina MSDN

Il motivo del design potrebbe essere solo una questione di separazione delle preoccupazioni e della velocità di una struttura.


1
Grazie. Si noti che un'altra risposta dice che tecnicamente (in IL), CancelTokenSource non imposta lo stato dei token; invece, i token racchiudono un riferimento effettivo a CancelTokenSource e accedono semplicemente ad esso per verificare la cancellazione. Semmai, la velocità può essere persa solo qui.
Andrey Tarantsov,

+1. Non capisco. se è un tipo di valore, quindi ogni metodo ha il suo valore (valore separato). quindi come fa un metodo a sapere se è stato chiamato un annullamento? NON ha alcun riferimento a TokenSource. tutto il metodo che può vedere è un tipo di valore locale come "5". Puoi spiegare ?
Royi Namir,

1
@RoyiNamir: ogni cancellazioneToken ha riferimenti privati ​​"m_source" di tipo CancellazioneTokenSource
Andreyul

2

La CancellationTokenSourceè la 'cosa' che le questioni della cancellazione, per qualsiasi motivo. Ha bisogno di un modo per "spedire" tale cancellazione a tutto CancellationTokenciò che ha emesso. Ecco come, ad esempio, ASP.NET può annullare le operazioni quando viene interrotta una richiesta. Ogni richiesta ha un CancellationTokenSourceche inoltra la cancellazione a tutti i token che ha emesso.

Questo è ottimo per i test di unità BTW: crea la tua sorgente token di annullamento, ottieni un token, chiama Cancell'origine e passa il token al tuo codice che deve gestire la cancellazione.


Grazie - ma entrambe le responsabilità (che sono molto correlate) potrebbero essere assegnate alla stessa classe. Non spiega davvero perché sono separati.
Andrey Tarantsov,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.