2011-08-05 29 views
105

La clase CancellationTokenSource es desechable. Un vistazo rápido en Reflector demuestra el uso de KernelEvent, un recurso (muy probable) no administrado. Dado que CancellationTokenSource no tiene finalizador, si no lo desechamos, el GC no lo hará.¿Cuándo deshacerse de CancellationTokenSource?

Por otro lado, si mira los ejemplos enumerados en el artículo Cancellation in Managed Threads de MSDN, solo un fragmento de código descarta el token.

¿Cuál es la forma correcta de eliminarlo en el código?

  1. No se puede envolver código de iniciar su tarea paralela con using si no esperarlo. Y tiene sentido tener cancelación solo si no esperas.
  2. Por supuesto, puede agregar ContinueWith en la tarea con una llamada Dispose, pero ¿es así?
  3. ¿Qué sucede con las consultas cancelables de PLINQ, que no se sincronizan hacia atrás sino que simplemente hacen algo al final? Digamos .ForAll(x => Console.Write(x))?
  4. ¿Es reutilizable? ¿Se puede usar el mismo token para varias llamadas y luego disponerlo junto con el componente de host, digamos el control de UI?

Debido a que no tiene algo así como un método Reset a la limpieza IsCancelRequested y Token campo yo supongo que es no reutilizable, por lo tanto cada vez que inicia una tarea (o una consulta PLINQ) se debe crear uno nuevo . ¿Es verdad? En caso afirmativo, mi pregunta es ¿cuál es la estrategia correcta y recomendada para tratar con Dispose en esas muchas instancias de CancellationTokenSource?

Respuesta

45

Hablando sobre si es realmente necesario llamar a Dispose en CancellationTokenSource ... Tuve una pérdida de memoria en mi proyecto y resultó que CancellationTokenSource era el problema.

Mi proyecto tiene un servicio, que constantemente lee bases de datos y despide diferentes tareas, y estaba pasando tokens de cancelación vinculados a mis trabajadores, por lo tanto, incluso después de que terminaron de procesar los datos, no se eliminaron los tokens de cancelación. una pérdida de memoria

MSDN Cancellation in Managed Threads afirma claramente:

Tenga en cuenta que debe llamar Dispose de la fuente de señal ligada cuando haya terminado con él. Para un ejemplo más completo, vea How to: Listen for Multiple Cancellation Requests.

He utilizado ContinueWith en mi implementación.

+10

Esta es una omisión importante en la respuesta aceptada actualmente por Bryan Crosby: si crea un CTS _linked_, corre el riesgo de pérdidas de memoria.El escenario es muy similar a los controladores de eventos que nunca están sin registrar. –

+3

Tuve una fuga debido a este mismo problema. Utilizando un generador de perfiles pude ver registros de devolución de llamada que contienen referencias a las instancias CTS vinculadas. Examinar el código para la implementación de Disposición de CTS [aquí] (http://referencesource.microsoft.com/#mscorlib/system/threading/CancellationTokenSource.cs,548) fue muy revelador y pone de relieve la comparación de @SørenBoisen con las filtraciones de registro de controlador de eventos. – BitMask777

+0

Los comentarios anteriores reflejan el estado de la discusión cuando se aceptó la otra respuesta de @Bryan Crosby. –

23

Me tomó un vistazo en ILSpy para la CancellationTokenSource pero sólo puedo encontrar m_KernelEvent que es en realidad un ManualResetEvent, que es una clase contenedora para un objeto WaitHandle. Esto debe ser manejado apropiadamente por el GC.

+5

Tengo la misma sensación de que GC limpiará todo eso. Intentaré verificar eso. ¿Por qué Microsoft implementó disponer en este caso? Para deshacerse de las devoluciones de llamadas de eventos y evitar la propagación a GC de segunda generación, probablemente. En este caso, llamar a Dispose es opcional; llámalo si puedes, si no solo ignóralo. No es la mejor manera, creo. –

+2

He investigado este problema. CancellationTokenSource obtiene basura recolectada. Puede ayudar con disponer de hacerlo en GEN 1 GC. Aceptado. –

+1

Hice esta misma investigación de forma independiente y llegué a la misma conclusión: deseche si puede hacerlo fácilmente, pero no se preocupe por tratar de hacerlo en los casos raros, pero no desconocidos, en los que ha enviado un CancelaciónToken. en los boondocks y no quiero esperar a que escriban una postal diciéndote que ya terminaron. Esto sucederá de vez en cuando debido a la naturaleza de lo que se usa CancellationToken, y está realmente bien, lo prometo. –

1

Cree una nueva aplicación de Windows Forms desde la plantilla del proyecto. Coloque un botón en el formulario y haga doble clic en él. Haga que se vea así:

private void button1_Click(object sender, EventArgs e) { 
     var t = new System.Threading.Thread(() => { }); 
     t.Start(); 
    } 

Presione Ctrl + F5 para iniciarlo. Start + Run, TaskMgr.exe, pestaña Procesos. Ver + Seleccionar columnas y marcar "Manejar". Observe el valor de esta columna para el proceso de WindowsFormsApplication1.exe mientras hace clic repetidamente en el botón.

La clase Thread no tiene un método Dispose().

Vamos a trabajar asumiendo que tenía uno. ¿Cuándo lo llamarías?


Leer más acerca de la conveniencia de tratar de eliminar los objetos difíciles de eliminar en this blog post por Stephen Toub.

+4

¿Pero por qué 'CancellationTokenSource' implementa' IDisposable' entonces? No debería necesitarlo. –

+3

¿Es una respuesta? ¿Quiere decir que no debe llamar a deshacerse de CancelationTokenSource? Eliminar el método de CancelationToken tiene una funcionalidad. ¿Es innecesario? Mire en reflector - microsoft lo usa intensamente. –

+2

@ Frédéric - CTS tiene un recurso desechable. Cuatro menos que un hilo. –

12

Siempre debe disponer CancellationTokenSource.

cómo deshacerse depende exactamente en el escenario. Usted propone varios escenarios diferentes.

  1. using sólo funciona cuando se está utilizando CancellationTokenSource en un trabajo paralelo que usted está esperando. Si ese es tu senario, genial, es el método más fácil.

  2. Al usar tareas, use una tarea ContinueWith como indicó para deshacerse de CancellationTokenSource.

  3. Para plinq puede usar using ya que lo está ejecutando en paralelo pero esperando a que finalicen todos los trabajadores en paralelo.

  4. Para la IU, puede crear una nueva CancellationTokenSource para cada operación cancelable que no esté vinculada a un único activador de cancelación. Mantenga un List<IDisposable> y agregue cada fuente a la lista, eliminándolos cuando se elimine su componente.

  5. Para roscas, cree un nuevo hilo que une a todos los subprocesos de trabajo y se cierra la fuente única cuando todos los subprocesos de trabajo terminado. Ver CancellationTokenSource, When to dispose?

Siempre hay un camino. IDisposable instancias siempre deben eliminarse. Las muestras a menudo no lo hacen porque son muestras rápidas para mostrar el uso del núcleo o porque agregar todos los aspectos de la clase demostrada sería demasiado complejo para una muestra. La muestra es solo una muestra, no necesariamente (o incluso por lo general) código de calidad de producción. No todas las muestras son aceptables para copiarse en el código de producción como está.

+0

para el punto 2, ¿alguna razón por la que no pudo usar 'await' en la tarea y disponer el CancellationTokenSource en el código que viene después de la espera? – stijn

+0

@stijn, sí, eso funcionaría también. –

+9

Hay advertencias. Si el CTS se cancela mientras esperas una operación, puedes reanudarlo debido a una 'OperationCanceledException'. A continuación, puede llamar a 'Dispose()'. Pero si todavía hay operaciones ejecutándose y usando el 'CancellationToken' correspondiente, ese token aún informa' CanBeCanceled' como 'verdadero' aunque el origen esté dispuesto. Si intentan registrar una devolución de llamada de cancelación, ** BOOM! **, 'ObjectDisposedException'. Es lo suficientemente seguro para llamar a 'Dispose()' después de la finalización exitosa de la (s) operación (es). Se vuelve * realmente complicado * cuando realmente necesitas cancelar algo. –

7

esta pregunta todavía está subiendo en las búsquedas de Google, y creo que la respuesta es votado hasta no da la historia completa. Después de mirar sobre la source code para CancellationTokenSource (CTS) y CancellationToken (CT) Creo que para la mayoría de los casos de uso de la siguiente secuencia de código está bien:

if (cancelTokenSource != null) 
{ 
    cancelTokenSource.Cancel(); 
    cancelTokenSource.Dispose(); 
    cancelTokenSource = null; 
} 

El campo interno m_kernelHandle mencionado anteriormente es el objeto de sincronización copias de la propiedad WaitHandle en las clases CTS y CT. Solo se crea una instancia si accede a esa propiedad. Por lo tanto, a menos que esté usando WaitHandle para sincronizar algunos hilos de la vieja escuela en su Task, deshacerse de ellos no tendrá efecto.

Por supuesto, si son su utilización, usted debe hacer lo que se sugiere por las otras respuestas arriba y retraso llamando Dispose hasta que cualquier WaitHandle operaciones utilizando el mango están completas, porque, como se describe en el Windows API documentation for WaitHandle, los resultados están indefinidos

+6

El artículo de MSDN [Cancelación de hilos gestionados] (https://msdn.microsoft.com/en-us/library/dd997364.aspx) establece: "Los oyentes supervisan el valor de la propiedad' IsCancellationRequested' del token mediante sondeo, devolución de llamada, o espera manejar ". En otras palabras: puede que no sea usted (es decir, el que realiza la solicitud asincrónica) quien utiliza el identificador de espera, puede ser el oyente (es decir, el que responde la solicitud). Lo que significa que usted, como responsable de la eliminación, no tiene control sobre si se usa el identificador de espera o no. – herzbube

+0

De acuerdo con MSDN, las devoluciones de llamada registradas que tienen una excepción provocarán la emisión de .Cancelar. Su código no llamará .Dispose() si esto sucede. Las devoluciones de llamada deben tener cuidado de no hacer esto, pero puede suceder. –

21

No creo que ninguna de las respuestas actuales fuera satisfactoria. Después de investigar encontré esta respuesta de Stephen Toub (reference):

Depende. En .NET 4, CTS.Dispose sirvió dos propósitos principales. Si se ha accedido al CancelToken's WaitHandle (por lo tanto, lo ha alojado alocadamente), Dispose desechará ese identificador. Además, si el CTS se creó a través del método CreateLinkedTokenSource, Dispose desvinculará el CTS de los tokens a los que se vinculó. En .NET 4.5, Dispose tiene un propósito adicional, que es si el CTS usa un temporizador debajo de las cubiertas (por ejemplo, CancelAfter fue llamado), el temporizador será Disposed.

Es muy raro que se use CancellationToken.WaitHandle, , por lo que limpiarlo después de que normalmente no sea una buena razón para usar Dispose. Si, sin embargo, está creando su CTS con CreateLinkedTokenSource, o si está utilizando la funcionalidad de temporizador del CTS, puede ser más impactante usar para usar Dispose.

La parte en negrita creo que es la parte importante. Él usa "más impactante", lo que lo deja un poco vago. Estoy interpretando que significa que se debe llamar al Dispose en esas situaciones, de lo contrario, no es necesario usar Dispose.

+1

Más impactante significa que el niño CTS se agrega al padre uno. Si no dispone de un niño, habrá una fuga si el padre es de larga vida. Por lo tanto, es fundamental eliminar los enlaces. – Grigory