2012-05-27 12 views
8

Creo que vi un error grave en TPL. No estoy seguro. Pasé mucho tiempo rascándome la cabeza y no puedo entender el comportamiento. ¿Alguien puede ayudar?Error en TPL - TaskContinuationOptions.ExecuteSynchronously?

Lo que mi escenario es:

  1. creo una tarea que hace cosa simple. Sin excepciones, etc.
  2. Registre una continuación con ExecuteSynchronously set. Debe estar en el mismo hilo.
  3. Comienzo la tarea en el taskcheduler predeterminado (ThreadPool). El hilo de inicio continúa y lo espera.
  4. Comienza la tarea. Pases.
  5. La continuación comienza en el mismo subproceso que la tarea (completando la tarea anterior) y entra en un bucle infinito.
  6. No ocurre nada con el hilo de espera. No quiere ir más allá. Atascado en espera. Revisé el depurador, la tarea es RunToCompletion.

Aquí está mi código. Apreciar cualquier ayuda!

// note: using new Task() and then Start() to avoid race condition dangerous 
// with TaskContinuationOptions.ExecuteSynchronously flag set on continuation. 

var task = new Task(() => { /* just return */ }); 
task.ContinueWith(
    _task => { while (true) { } /* never return */ }, 
    TaskContinuationOptions.ExecuteSynchronously); 

task.Start(TaskScheduler.Default); 
task.Wait(); // a thread hangs here forever even when EnterEndlessLoop is already called. 
+0

También creo que esto es un error. Aquí está la página de documentación: http://msdn.microsoft.com/en-us/library/system.threading.tasks.taskcontinuationoptions.aspx Desafortunadamente, no dice nada sobre este caso. – usr

+0

¿Hay alguna posibilidad de que pueda expandir el fragmento de código para que sea lo suficientemente completo como para que otros copien y peguen y se ejecuten solos? –

+0

@usr - dice "Solo las ejecuciones de ejecución muy corta deben ejecutarse sincrónicamente". :) Estoy de acuerdo en que es un error, sin embargo. –

Respuesta

5

envió un bug en conectar en su nombre - la esperanza de que está bien :)

https://connect.microsoft.com/VisualStudio/feedback/details/744326/tpl-wait-call-on-task-doesnt-return-until-all-continuations-scheduled-with-executesynchronously-also-complete

Estoy de acuerdo que es un error, sólo quería publicar un fragmento de código que muestra el problema sin tener una espera 'interminable'. El error es que un ExecuteSynchronously significa que las llamadas de espera en la primera tarea no regresan hasta que las ejecuciones de ExecuteSynchronously también se completen.

Al ejecutar el siguiente fragmento muestra que espera 17 segundos (por lo que se deben completar los 3 en lugar de solo el primero). Es lo mismo si la tercera tarea está programada para el primero o para el segundo (por lo que este ExecuteSynchronously continuará a través del árbol de tareas programadas como tales).

void Main() 
{ 
    var task = new Task(() => Thread.Sleep(2 * 1000)); 
    var secondTask = task.ContinueWith(
     _ => Thread.Sleep(5 * 1000), 
     TaskContinuationOptions.ExecuteSynchronously); 
    var thirdTask = secondTask.ContinueWith(
     _ => Thread.Sleep(10 * 1000), 
     TaskContinuationOptions.ExecuteSynchronously); 

    var stopwatch = Stopwatch.StartNew(); 
    task.Start(TaskScheduler.Default); 
    task.Wait(); 
    Console.WriteLine ("Wait returned after {0} seconds", stopwatch.ElapsedMilliseconds/1000.0); 
} 

La única cosa que me hace pensar que podría ser intencional (y por lo tanto más de un error doc de error de código) es Stephen's comment in this blog post:

ExecuteSynchronously es una solicitud para una optimización para ejecutar el tarea de continuación en el mismo subproceso que completó el antecedente tarea de la que continuamos, en efecto ejecutando la continuación como parte de la transición del antecedente a af estado inal

0

Así que resulta que es un error. Creo que todos están de acuerdo. Sin embargo, si esto tenía la intención de comportarse así, la API y los documentos parecen ser muy engañosos.

El trabajo que he usado es simplemente usar ManualResetEventSlim.

var eventSlim = new ManualResetEventSlim(false); 
var task = new Task(() => { eventSlim.Set(); }); 
task.ContinueWith(
    _task => { while (true) { } /* never return */ }, 
    TaskContinuationOptions.ExecuteSynchronously); 

task.Start(TaskScheduler.Default); 
eventSlim.Wait(); 

¡Gracias a todos por echar un vistazo a esto! Y para todos los comentarios.

Atentamente.

3

Este comportamiento tiene sentido si tenemos en cuenta task inlining. Cuando llame al Task.Wait antes de que la tarea haya comenzado la ejecución, el planificador intentará alinearla, es decir, ejecutarla en el mismo hilo que llamó al Task.Wait. Esto tiene sentido: ¿por qué perder un hilo esperando la tarea cuando puedes reutilizar el hilo para ejecutar la tarea?

Ahora, cuando se especifica ExecuteSynchronously, se ordena al planificador que ejecute la continuación en el mismo subproceso que la tarea antecedente, que es la cadena de llamada original en el caso de la alineación.

Tenga en cuenta que cuando no se lleva a cabo la alineación, el comportamiento que esperaba tiene lugar. Todo lo que tienes que hacer es prohibir la alineación, y eso es fácil, ya sea especificar un tiempo de espera para esperar o pasar un token de cancelación, p.

task.Wait(new CancellationTokenSource().Token); //This won't wait for the continuation 

Por último, tenga en cuenta que la alineación no está garantizada. En mi máquina, no sucedió porque la tarea ya comenzó antes de la llamada Wait, por lo que mi llamada Wait no se bloqueó. Si desea un bloque reproducible, llame al Task.RunSynchronously.