La clase CancellationTokenSource
es desechable. Un vistazo rápido en Reflector demuestra el uso de KernelEvent
un recurso no administrado (muy probable). Como CancellationTokenSource
no tiene finalizador, si no lo desechamos, el GC no lo hará.
Por otro lado, si observa las muestras enumeradas en el artículo Cancelación de MSDN en Subprocesos administrados , solo un fragmento de código elimina el token.
¿Cuál es la forma correcta de deshacerse de él en código?
- No puede ajustar el código con el que comienza su tarea paralela
using
si no lo espera. Y tiene sentido tener cancelación solo si no espera. - Por supuesto, puede agregar una
ContinueWith
tarea con unaDispose
llamada, pero ¿es ese el camino a seguir? - ¿Qué pasa con las consultas PLINQ cancelables, que no se sincronizan de nuevo, sino que simplemente hacen algo al final? Vamos a decir
.ForAll(x => Console.Write(x))
? - ¿Es reutilizable? ¿Se puede usar el mismo token para varias llamadas y luego desecharlo junto con el componente host, digamos control UI?
Debido a que no tiene algo como un Reset
método de limpieza IsCancelRequested
y Token
campo, supongo que no es reutilizable, por lo tanto, cada vez que inicie una tarea (o una consulta PLINQ), debe crear una nueva. ¿Es verdad? En caso afirmativo, mi pregunta es ¿cuál es la estrategia correcta y recomendada para tratar Dispose
en esos CancellationTokenSource
casos?
fuente
Important: The CancellationTokenSource class implements the IDisposable interface. You should be sure to call the CancellationTokenSource.Dispose method when you have finished using the cancellation token source to free any unmanaged resources it holds.
- docs.microsoft.com/en-us/dotnet/standard/threading/…No pensé que ninguna de las respuestas actuales fuera satisfactoria. Después de investigar encontré esta respuesta de Stephen Toub ( referencia ):
La parte audaz creo que es la parte importante. Él usa "más impactante", lo que lo deja un poco vago. Lo estoy interpretando como que
Dispose
se debe hacer un llamado en esas situaciones, de lo contrarioDispose
no es necesario usarlo .fuente
Eché un vistazo en ILSpy para el,
CancellationTokenSource
pero solo puedo encontrarm_KernelEvent
cuál es realmente unaManualResetEvent
, que es una clase de contenedor para unWaitHandle
objeto. Esto debe ser manejado adecuadamente por el GC.fuente
Siempre debes disponer
CancellationTokenSource
.Cómo desecharlo depende exactamente del escenario. Propones varios escenarios diferentes.
using
solo funciona cuando está utilizandoCancellationTokenSource
algún trabajo paralelo que está esperando. Si ese es tu senario, entonces genial, es el método más fácil.Cuando use tareas, use una
ContinueWith
tarea como indicó para deshacerse de ellaCancellationTokenSource
.Para plinq puedes usarlo
using
ya que lo estás ejecutando en paralelo pero esperando a que todos los trabajadores de ejecución paralela terminen.Para la IU, puede crear una nueva
CancellationTokenSource
para cada operación cancelable que no esté vinculada a un solo desencadenador de cancelación. Mantenga unaList<IDisposable>
y agregue cada fuente a la lista, eliminándolas todas cuando se elimine su componente.Para los subprocesos, cree un nuevo subproceso que se una a todos los subprocesos de trabajo y cierre la fuente única cuando finalicen todos los subprocesos de trabajo. Ver CancellationTokenSource, ¿Cuándo desechar?
Siempre hay una manera.
IDisposable
Las instancias siempre deben desecharse. Las muestras a menudo no lo hacen porque son muestras rápidas para mostrar el uso principal o porque agregar todos los aspectos de la clase que se está demostrando sería demasiado complejo para una muestra. La muestra es solo una muestra, no necesariamente (o incluso generalmente) el código de calidad de producción. No todas las muestras son aceptables para copiarse en el código de producción tal cual.fuente
await
en la tarea y desechar el CancellationTokenSource en el código que viene después de la espera?await
una operación, puede reanudarlo debido a unOperationCanceledException
. Entonces puedes llamarDispose()
. Pero si todavía hay operaciones que se ejecutan y usan el correspondienteCancellationToken
, ese token aún se informaCanBeCanceled
comotrue
aunque la fuente esté eliminada. Si intentan registrar una devolución de llamada de cancelación, BOOM! ,ObjectDisposedException
. Es lo suficientemente seguro para llamarDispose()
después de completar con éxito las operaciones. Se vuelve realmente complicado cuando realmente necesitas cancelar algo.Esta respuesta sigue apareciendo en las búsquedas de Google, y creo que la respuesta votada no da la historia completa. Después de revisar el código fuente de
CancellationTokenSource
(CTS) yCancellationToken
(CT), creo que para la mayoría de los casos de uso, la siguiente secuencia de código está bien:El
m_kernelHandle
campo interno mencionado anteriormente es el objeto de sincronización que respalda laWaitHandle
propiedad en las clases CTS y CT. Solo se crea una instancia si accede a esa propiedad. Por lo tanto, a menos que esté utilizandoWaitHandle
para algunos sincronización de hilos de la vieja escuela en suTask
disponer de llamada no tendrá ningún efecto.Por supuesto, si lo está utilizando, debe hacer lo que sugieren las otras respuestas anteriores y retrasar la llamada
Dispose
hasta queWaitHandle
se complete cualquier operación que use el identificador, porque, como se describe en la documentación de la API de Windows para WaitHandle , los resultados no están definidos.fuente
IsCancellationRequested
propiedad del token mediante sondeo, devolución de llamada o espera". En otras palabras: puede que no sea usted (es decir, el que realiza la solicitud asíncrona) quien utiliza el identificador de espera, puede ser el oyente (es decir, el que responde la solicitud). Lo que significa que usted, como el responsable de la eliminación, no tiene control sobre si se utiliza o no el tirador de espera.Ha pasado mucho tiempo desde que pregunté esto y obtuve muchas respuestas útiles, pero me encontré con un problema interesante relacionado con esto y pensé en publicarlo aquí como otra respuesta:
Debe llamar
CancellationTokenSource.Dispose()
solo cuando esté seguro de que nadie intentará obtener laToken
propiedad del CTS . De lo contrario, usted debe no la llamas, porque es una carrera. Por ejemplo, mira aquí:https://github.com/aspnet/AspNetKatana/issues/108
En la solución para este problema, el código que lo hizo anteriormente
cts.Cancel(); cts.Dispose();
fue editado solocts.Cancel();
porque cualquiera que tenga la mala suerte de intentar obtener el token de cancelación para observar su estado de cancelación después deDispose
haber sido llamado, desafortunadamente también tendrá que manejarloObjectDisposedException
, además deOperationCanceledException
que estaban planeandoTratcher hace otra observación clave relacionada con esta solución: "La eliminación solo es necesaria para los tokens que no se cancelarán, ya que la cancelación hace la misma limpieza". es decir, ¡hacer en
Cancel()
lugar de deshacerse es bastante bueno!fuente
Hice una clase segura para subprocesos que une a
CancellationTokenSource
aTask
, y garantiza queCancellationTokenSource
se eliminará cuando seTask
complete su asociado . Utiliza cerraduras para garantizar queCancellationTokenSource
no se cancele durante o después de que se haya eliminado. Esto sucede para cumplir con la documentación , que establece:Y también :
Aquí está la clase:
Los métodos principales de la
CancelableExecution
clase son elRunAsync
y elCancel
. Por defecto, las operaciones concurrentes no están permitidas, lo que significa que llamarRunAsync
por segunda vez se cancelará silenciosamente y esperará la finalización de la operación anterior (si aún se está ejecutando), antes de comenzar la nueva operación.Esta clase se puede usar en aplicaciones de cualquier tipo. Sin embargo, su uso principal es en aplicaciones de IU, dentro de formularios con botones para iniciar y cancelar una operación asincrónica, o con un cuadro de lista que cancela y reinicia una operación cada vez que se cambia su elemento seleccionado. Aquí hay un ejemplo del primer caso:
El
RunAsync
método acepta un extraCancellationToken
como argumento, que está vinculado a lo creado internamenteCancellationTokenSource
. El suministro de este token opcional puede ser útil en escenarios avanzados.fuente