with run example cancellationtoken cancel .net multithreading task-parallel-library cancellationtokensource cancellation-token

.net - run - cancellationtoken c#



¿Por qué CancellationToken está separado de CancellationTokenSource? (6)

El CancellationTokenSource es la ''cosa'' que emite la cancelación, por la razón que sea. Necesita una forma de ''enviar'' esa cancelación a todos los CancellationToken que ha emitido. Así es como, por ejemplo, ASP.NET puede cancelar operaciones cuando se cancela una solicitud. Cada solicitud tiene un CTSource que remite la cancelación a todos los tokens que ha emitido.

Esto es excelente para pruebas de unidades BTW: cree su propia fuente de token de cancelación, obtenga un token, llame a Cancelar en soource y pase el token a su código que debe encargarse de la cancelación.

Estoy buscando una explicación de por qué .NET CancellationToken struct se introdujo además de la clase CancellationTokenSource. Entiendo cómo se usará el API, pero también quiero entender por qué está diseñado de esa manera.

Es decir, ¿por qué tenemos:

var cts = new CancellationTokenSource(); SomeCancellableOperation(cts.Token); ... public void SomeCancellableOperation(CancellationToken token) { ... token.ThrowIfCancellationRequested(); ... }

en lugar de pasar directamente CancelaciónTokenSource como:

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

¿Se trata de una optimización del rendimiento basada en el hecho de que las verificaciones de estado de cancelación ocurren con más frecuencia que pasar el token?

¿Para que CancellationTokenSource pueda realizar un seguimiento y actualizar CancellationTokens, y para cada token la verificación de cancelación es un acceso de campo local?

Dado que un bool volátil sin bloqueo es suficiente en ambos casos, todavía no puedo ver por qué sería más rápido.

¡Gracias!


Están separados no por razones técnicas sino semánticas. Si nos fijamos en la implementación de CancellationToken bajo ILSpy, encontrará que es simplemente un envoltorio alrededor de CancellationTokenSource (y por lo tanto no es diferente en cuanto a rendimiento que pasar una referencia).

Proporcionan esta separación de funciones para hacer que las cosas sean más predecibles: cuando pasas un método a CancellationToken , sabes que todavía eres el único que puede cancelarlo. Claro, el método aún podría arrojar una TaskCancelledException , pero el CancellationToken sí mismo y cualquier otro método que haga referencia al mismo token, se mantendría seguro.


Estuve involucrado en el diseño e implementación de estas clases.

La respuesta corta es "separación de preocupaciones". Es bastante cierto que hay varias estrategias de implementación y que algunas son más simples, al menos en lo que respecta al sistema de tipos y el aprendizaje inicial. Sin embargo, CTS y CT están diseñados para su uso en una gran cantidad de escenarios (como pilas de bibliotecas profundas, cálculos paralelos, asincrónicos, etc.) y, por lo tanto, se diseñaron con muchos casos de uso complejos en mente. Es un diseño destinado a alentar patrones exitosos y desalentar antipatrones sin sacrificar el rendimiento.

Si la puerta se deja abierta por comportarse mal con las API, entonces la utilidad del diseño de cancelación podría verse erosionada rápidamente.


Mi "teoría" es que el rol de CancellationToken es proporcionar un contenedor seguro para subprocesos para evitar conflictos .

Si hubiera una sola clase, por lo que se usaría una instancia única con copia de referencia, y tiene muchos hilos que registran los controladores para la cancelación, por ejemplo, esta clase debe administrar la concurrencia usando algún bloqueo, reduciendo el rendimiento.

Mientras que con la estructura CancellationToken tienes una copia de cada callbacks-queue para cada hilo, por lo que no hay contención.

Esto es pura especulación y puedo estar completamente equivocado y / o puede haber otras buenas razones para este diseño.

Lo único seguro es que hay al menos una buena razón, y es una pena que este tipo de decisión no esté más documentada, ya que es una valiosa idea al usar una tecnología.


Tenía exactamente la misma pregunta y quería entender la razón de este diseño.

La respuesta aceptada tiene la razón de ser exactamente correcta. Aquí está la confirmación del equipo que diseñó esta función (el énfasis es mío):

Dos nuevos tipos forman la base del marco: Un CancellationToken es una estructura que representa una ''posible solicitud de cancelación''. Esta estructura se pasa a las llamadas a métodos como un parámetro y el método puede sondear o registrar una devolución de llamada que se activará cuando se solicite la cancelación. Un CancellationTokenSource es una clase que proporciona el mecanismo para iniciar una solicitud de cancelación y tiene una propiedad Token para obtener un token asociado. Hubiera sido natural combinar estas dos clases en una sola, pero este diseño permite que las dos operaciones clave (iniciando una solicitud de cancelación vs. observando y respondiendo a la cancelación) se separen limpiamente. En particular, los métodos que solo toman un CancellationToken pueden observar una solicitud de cancelación pero no pueden iniciarla.

Enlace: .NET 4 Cancellation Framework

En mi opinión, el hecho de que CancellationToken solo pueda observar el estado y no cambiarlo es extremadamente crítico. Puedes repartir el token como si fuera un caramelo y no te preocupes porque alguien más, aparte de ti, lo cancelará. Le protege del código hostil de terceros. Sí, las posibilidades son escasas, pero personalmente me gusta esa garantía.

También creo que hace que la API sea más limpia y evita errores accidentales y promueve un mejor diseño de los componentes.

Miremos la API pública para ambas clases.

Si tuviera que combinarlos, al escribir LongRunningFunction, veré métodos como esas sobrecargas múltiples de ''Cancelar'' que no debería estar usando. Personalmente, odio ver el método Dispose también.

Creo que el diseño actual de la clase sigue la filosofía del "pozo del éxito", que guía a los desarrolladores a crear mejores componentes que puedan manejar la cancelación de Tareas y luego instrumentarlos de muchas maneras para crear flujos de trabajo complicados.

Déjame hacerte una pregunta, ¿te has preguntado cuál es el propósito de token. Regístrese? No tiene sentido para mí. Y luego leí Cancelación en Hilos Administrados y todo se volvió claro como el agua.

Creo que el Diseño del Marco de Cancelación en TPL es absolutamente de primera clase.


The CancellationToken es una estructura de modo que podrían existir muchas copias debido a su transferencia a los métodos.

El CancellationTokenSource establece el estado de TODAS las copias de un token al llamar a Cancelar en el origen. Ver esta página MSDN

La razón del diseño podría ser simplemente una cuestión de separación de preocupaciones y la velocidad de una estructura.