run - cancel task c#
¿Cuándo deshacerse de CancellationTokenSource? (6)
La clase CancellationTokenSource
es desechable. Un vistazo rápido en Reflector demuestra el uso de KernelEvent
, un KernelEvent
(muy probable) no administrado. Dado que CancellationTokenSource
no tiene un finalizador, si no lo eliminamos, el GC no lo hará.
Por otro lado, si observa los ejemplos enumerados en la cancelación de artículos de MSDN en hilos gestionados , solo un fragmento de código descarta el token.
¿Cuál es la forma correcta de eliminarlo en el código?
- No puede ajustar el código comenzando su tarea paralela con el
using
si no lo espera. Y tiene sentido tener cancelación solo si no esperas. - Por supuesto, puede agregar
ContinueWith
en la tarea con una llamada aDispose
, pero ¿es ese el camino a seguir? - ¿Qué sucede con las consultas cancelables de PLINQ, que no se sincronizan sino que simplemente hacen algo al final? Digamos
.ForAll(x => Console.Write(x))
? - ¿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?
Como no tiene algo así como un método de Reset
para limpiar el campo IsCancelRequested
y Token
, supongo que no es reutilizable, por lo que 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 el tema Dispose
en esas muchas instancias de CancellationTokenSource
?
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. Haz 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 desde la suposición de que tenía uno. ¿Cuándo lo llamarías?
Lea más sobre la sabiduría de tratar de deshacerse de los objetos difíciles de eliminar en reference de Stephen Toub.
Eché un vistazo en ILSpy para el CancellationTokenSource
pero solo puedo encontrar m_KernelEvent, que en realidad es un ManualResetEvent
, que es una clase contenedora para un objeto WaitHandle. Esto debe ser manejado apropiadamente por el GC.
Esta respuesta todavía está apareciendo en las búsquedas de Google, y creo que la respuesta votada no brinda la historia completa. Después de revisar el código fuente de CancellationTokenSource
(CTS) y CancellationToken
(CT) creo que para la mayoría de los casos de uso, 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 que respalda 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 alguna sincronización de hilos de la vieja escuela en su llamada de Task
disponer no tendrá ningún efecto.
Por supuesto, si lo está utilizando, debe hacer lo que sugieren las otras respuestas anteriores y retrasar la invocación de Dispose
hasta que se WaitHandle
todas WaitHandle
operaciones WaitHandle
que utilizan el handle, porque, como se describe en la documentación API de Windows para WaitHandle , los resultados no están definidos .
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 la base de datos y dispara diferentes tareas, y estaba pasando tokens de cancelación vinculados a mis trabajadores, por lo que incluso después de que habían terminado de procesar los datos, los tokens de cancelación no se eliminaban, lo que causaba una pérdida de memoria.
La cancelación de MSDN en Managed Threads lo dice claramente:
Tenga en cuenta que debe llamar a
Dispose
en la fuente del token vinculado cuando haya terminado con él. Para obtener un ejemplo más completo, consulte Cómo: Escuchar múltiples solicitudes de cancelación .
Usé ContinueWith
en mi implementación.
No pensé 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ó para dos propósitos principales. Si se ha accedido al WaitHandle de CancellationToken (asignándolo de forma perezosa), Dispose eliminará ese handle. Además, si el CTS se creó mediante el 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, cancel-after fue llamado), el temporizador será Disposed.
Es muy raro que CancellationToken.WaitHandle se use, por lo que limpiarlo después de que normalmente no es una buena razón para usar Dispose. Sin embargo, si está creando su CTS con CreateLinkedTokenSource, o si está utilizando la funcionalidad del temporizador del CTS, puede ser más impactante usar Dispose.
La parte audaz, creo que es la parte importante. Él usa "más impactante", lo que lo deja un poco vago. Lo interpreto como que significa que llamar a Dispose
en esas situaciones debe hacerse, de lo contrario, no es necesario usar Dispose
.
Siempre debe desechar CancellationTokenSource
.
Cómo deshacerse depende exactamente del escenario. Usted propone varios escenarios diferentes.
using
solo funciona cuando usaCancellationTokenSource
en algún trabajo paralelo que está esperando. Si ese es tu senario, genial, es el método más fácil.Cuando utilice tareas, use una tarea
ContinueWith
como indicó para deshacerse deCancellationTokenSource
.Para plinq puedes utilizarlo ya que lo estás ejecutando en paralelo pero esperando a que terminen todos los trabajadores en paralelo.
Para la IU, puede crear un nuevo
CancellationTokenSource
para cada operación cancelable que no esté vinculada a un único activador de cancelación. Mantenga unaList<IDisposable>
y agregue cada fuente a la lista, eliminándolos cuando su componente sea eliminado.Para los subprocesos, cree un nuevo subproceso que una todos los subprocesos de trabajo y cierre el origen único cuando finalicen todos los subprocesos de trabajo. Ver CancellationTokenSource, ¿Cuándo desechar?
Siempre hay una manera. IDisposable
instancias IDisposable
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á.