2010-09-17 9 views
100

Quiero activar una tarea para que se ejecute en una cadena de fondo. No quiero esperar para completar las tareas.¿Se considera aceptable no invocar a Dispose() en un objeto TPL Task?

En .NET 3.5 que habría hecho esto:

ThreadPool.QueueUserWorkItem(d => { DoSomething(); }); 

En .NET 4 TPL es la forma sugerida. El patrón común que he visto recomendada es:

Task.Factory.StartNew(() => { DoSomething(); }); 

Sin embargo, el método StartNew() devuelve un objeto que implementa TaskIDisposable. Este parece ser pasado por alto por las personas que recomiendan este patrón. La documentación de MSDN en el método Task.Dispose() dice:

"Llame siempre a Dispose antes de lanzar su última referencia a la tarea."

No se puede invocar a deshacerse de una tarea hasta que se complete, por lo que tener el hilo principal en espera y deshacerse de la llamada se anularía al punto de hacer en una cadena de fondo en primer lugar. Tampoco parece haber ningún evento completado/terminado que pueda usarse para la limpieza.

La página MSDN en la clase Tarea no hace ningún comentario al respecto, y el libro "Pro C# 2010 ..." recomienda el mismo patrón y no hace comentarios sobre la eliminación de tareas.

Sé que si lo dejo, el finalizador lo atrapará al final, pero ¿esto va a volver y morderme cuando estoy haciendo mucho fuego? & ¿Olvidaste de este tipo de tareas y el hilo del finalizador se abruma?

Así que mis preguntas son:

  • ¿Es aceptable no llamar Dispose() en la clase Task en este caso? Y si es así, ¿por qué y hay riesgos/consecuencias?
  • ¿Hay alguna documentación que discuta esto?
  • ¿O hay una forma adecuada de deshacerse del objeto Task que me he perdido?
  • ¿O hay otra forma de hacer fuego & olvida las tareas con el TPL?
+0

relacionadas: [La forma correcta de fuego y olvidar] (http://stackoverflow.com/q/2630488/119738) (ver [respuesta] (http://stackoverflow.com/questions/ 2630488/the-correct-way-to-fire-and-forget-an-asynchronous-delegate/2630510 # 2630510)) –

Respuesta

88

Hay un debate sobre este in the MSDN forums.

Stephen Toub, un miembro del equipo pfj Microsoft tiene esto que decir:

Task.Dispose existe debido a la Tarea potencialmente envolver un evento manejar utiliza cuando la espera en la tarea a completa, en el evento que el hilo de espera realmente tiene que bloquear (como opuesto al giro o potencialmente ejecutando la tarea que está esperando). Si todo lo que hace es utilizar las continuaciones , ese identificador de evento nunca se asignará
...
es mejor confiar en la finalización para encargarse de las cosas.

Actualización (octubre de 2012)
Stephen Toub ha publicado un blog titulado Do I need to dispose of Tasks? que da algo más de detalle, y explica las mejoras en .Net 4.5.

En resumen: no necesita deshacerse de los objetos Task el 99% del tiempo.

Existen dos motivos principales para deshacerse de un objeto: liberar recursos no administrados de manera oportuna y determinista, y evitar el costo de ejecutar el finalizador del objeto. Ninguna de estas situaciones Task mayor parte del tiempo:

  1. A partir de .Net 4.5, la única vez que un Task asigna el descriptor de espera interna (el único recurso no administrado en el objeto Task) es cuando se utiliza explícitamente el IAsyncResult.AsyncWaitHandle de Task y
  2. El objeto Task en sí mismo no tiene un finalizador; el identificador está envuelto en un objeto con un finalizador, por lo tanto, a menos que esté asignado, no hay un finalizador para ejecutar.
+3

Gracias, interesante. No obstante, va en contra de la documentación de MSDN. ¿Hay alguna palabra oficial de MS o del equipo de .net que este sea un código aceptable? También está el punto planteado al final de la discusión que "¿qué pasa si la implementación cambia en una versión futura?" –

+0

En realidad, acabo de notar que el contestador en ese hilo de hecho funciona en Microsoft, aparentemente en el equipo de pfx, entonces supongo que esta es una respuesta oficial de algún tipo. Pero hay una sugerencia de que no funciona en todos los casos. Si hay una posible fuga, ¿es mejor simplemente volver a ThreadPool.QueueUserWorkItem, que sé que es seguro? –

+0

Sí, es muy extraño que haya una Disposición que no puedas llamar. Si echa un vistazo a las muestras aquí http://msdn.microsoft.com/en-us/library/dd537610.aspx y aquí http://msdn.microsoft.com/en-us/library/dd537609.aspx no están eliminando tareas. Sin embargo, los ejemplos de código en MSDN a veces demuestran técnicas muy malas. También el tipo que respondió la pregunta funciona para Microsoft. –

13

Este es el mismo tipo de problema que con la clase Thread. Consume 5 identificadores del sistema operativo pero no implementa IDisposable. Una 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 Tarea agrega un identificador a esto, un evento de reinicio manual interno. ¿Cuál es el recurso de 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. Yeah, don't bother.

Tenga en cuenta que debe estar interesado en la propiedad IsFaulted de la tarea. Es un tema bastante feo, puedes leer más sobre esto en este MSDN Library article. Una vez que lidie con esto correctamente, también debe tener un buen lugar en su código para deshacerse de las tareas.

+0

Gracias Hans. Una buena comparación allí. –

+4

Pero una tarea no crea un 'Thread' en la mayoría de los casos, usa ThreadPool. – svick

-1

Me encantaría ver a alguien pesan en la técnica mostrada en este post: Typesafe fire-and-forget asynchronous delegate invocation in C#

Parece que un simple método de extensión se encargará de todos los casos triviales de la interacción con las tareas y ser capaz de llamar a disponer en él .

public static void FireAndForget<T>(this Action<T> act,T arg1) 
{ 
    var tsk = Task.Factory.StartNew(()=> act(arg1), 
            TaskCreationOptions.LongRunning); 
    tsk.ContinueWith(cnt => cnt.Dispose()); 
} 
+2

Por supuesto que no puede deshacerse de la instancia 'Task' devuelta por' ContinueWith', pero ver la cita de Stephen Toub es la respuesta aceptada: no hay nada que deshacerse si nada lleva a cabo un bloqueo de espera en una tarea. – Richard

+1

Como Richard menciona, ContinueWith (...) también devuelve un segundo objeto Task que luego no se elimina. –

+1

Por lo tanto, como esto significa que el código ContinueWith es en realidad peor que redudant ya que causará que se cree otra tarea solo para deshacerse de la tarea anterior. Con la forma en que esto sucede, básicamente no sería posible introducir una espera de bloqueo en este bloque de código, aparte de si el delegado de acción que pasaste intenta manipular las tareas también correctas. –

Cuestiones relacionadas