Quiero activar una tarea para que se ejecute en un hilo de fondo. No quiero esperar a que se completen las tareas.
En .net 3.5 hubiera hecho esto:
ThreadPool.QueueUserWorkItem(d => { DoSomething(); });
En .net 4, el TPL es la forma sugerida. El patrón común que he visto recomendado es:
Task.Factory.StartNew(() => { DoSomething(); });
Sin embargo, el StartNew()
método devuelve un Task
objeto que implementa IDisposable
. Esto parece ser pasado por alto por las personas que recomiendan este patrón. La documentación de MSDN sobre el Task.Dispose()
método dice:
"Siempre llame a Eliminar antes de liberar su última referencia a la Tarea".
No puede invocar la disposición en una tarea hasta que se complete, por lo que, en primer lugar, hacer que el subproceso principal espere y elimine la llamada anularía el punto de hacerlo en un subproceso en segundo plano. Tampoco parece haber ningún evento completado / terminado que pueda usarse para la limpieza.
La página de MSDN en la clase Tarea no comenta sobre esto, y el libro "Pro C # 2010 ..." recomienda el mismo patrón y no hace ningún comentario sobre la eliminación de tareas.
Sé que si lo dejo, el finalizador lo atrapará al final, pero ¿volverá esto y me morderá cuando estoy haciendo mucho fuego y olvido tareas como esta y el hilo del finalizador se abruma?
Entonces mis preguntas son:
- ¿Es aceptable no llamar
Dispose()
a laTask
clase en este caso? Y si es así, ¿por qué hay riesgos / consecuencias? - ¿Hay alguna documentación que discuta esto?
- ¿O hay una forma adecuada de deshacerse del
Task
objeto que me he perdido? - ¿O hay otra forma de disparar y olvidar tareas con el TPL?
fuente
Respuestas:
Hay una discusión sobre esto en los foros de MSDN .
Stephen Toub, miembro del equipo pfx de Microsoft tiene esto que decir:
Actualización (octubre de 2012)
Stephen Toub ha publicado un blog titulado ¿Necesito deshacerme de las tareas? que proporciona más detalles y explica las mejoras en .Net 4.5.
En resumen: no es necesario deshacerse de los
Task
objetos el 99% del tiempo.Hay dos razones principales para disponer de un objeto: liberar recursos no administrados de manera oportuna y determinista, y evitar el costo de ejecutar el finalizador del objeto. Ninguno de estos se aplica a la
Task
mayoría de las veces:Task
asigna el controlador de espera interno (el único recurso no administrado en elTask
objeto) es cuando utiliza explícitamente elIAsyncResult.AsyncWaitHandle
deTask
, yTask
objeto en sí no tiene un finalizador; el controlador está envuelto en un objeto con un finalizador, por lo que, a menos que esté asignado, no hay finalizador para ejecutar.fuente
EndInvoke
en WinForms cuando se usaBeginInvoke
para ejecutar código en el hilo de la interfaz de usuario). (2) Stephen Toub es bastante conocido como un orador habitual sobre el uso efectivo de PFX (por ejemplo, en channel9.msdn.com ), por lo que si alguien puede dar una buena guía, entonces es él. Tenga en cuenta su segundo párrafo: hay veces que dejar las cosas al finalizador es mejor.Este es el mismo tipo de problema que con la clase Thread. Consume 5 manejadores del sistema operativo pero no implementa IDisposable. Buena decisión de los diseñadores originales, por supuesto, hay pocas formas razonables de llamar al método Dispose (). Tendría que llamar a Join () primero.
La clase Task agrega un identificador a esto, un evento de reinicio manual interno. Cuál es el recurso del sistema operativo más barato que existe. Por supuesto, su método Dispose () solo puede liberar ese identificador de evento, no los 5 identificadores que consume Thread. Sí, no te molestes .
Tenga cuidado de que debería estar interesado en la propiedad IsFaulted de la tarea. Es un tema bastante feo, puede leer más al respecto en este artículo de la Biblioteca de MSDN . Una vez que maneje esto adecuadamente, también debe tener un buen lugar en su código para deshacerse de las tareas.
fuente
Thread
en la mayoría de los casos, usa ThreadPool.Me encantaría ver a alguien sopesar la técnica que se muestra en esta publicación: Invocación de delegado asincrónico de tipo seguro para disparar y olvidar en C #
Parece que un método de extensión simple manejará todos los casos triviales de interactuar con las tareas y podrá llamar a disposed en él.
fuente
Task
instancia devuelta porContinueWith
, pero ver la cita de Stephen Toub es la respuesta aceptada: no hay nada que eliminar si nada realiza una espera de bloqueo en una tarea.Task disper = null; disper = tsk.ContinueWith(cnt => { cnt.Dispose(); disper.Dispose(); });