2010-07-30 12 views
16

Actualmente estoy escribiendo una aplicación que controlará el posicionamiento de un dispositivo de medición. Debido al hardware involucrado, necesito sondear constantemente el valor de la posición actual mientras estoy usando el motor eléctrico. Intento crear la clase responsable de esto para que realice el sondeo en un hilo de fondo y genere un evento cuando se alcanza la posición deseada. La idea es que el sondeo no bloqueará el resto de la aplicación o la GUI. Quería usar la nueva clase Threading.Task.Task para manejar todas las conexiones de fondo de subprocesos para mí..Net ¿por qué Threading.Task.Task aún bloquea mi UI?

Aún no tengo el hardware, pero tengo un compilador de prueba para simular este comportamiento. Pero cuando ejecuto la aplicación de esta manera, la GUI aún bloquea. Vea un ejemplo simplificado del código a continuación (no completo y sin usar una clase separada para el control del dispositivo). El código tiene una secuencia de pasos de medición, la aplicación debe posicionarse y luego medir para cada paso.

public partial class MeasurementForm: Form 
{ 
    private MeasurementStepsGenerator msg = new MeasurementsStepGenerator(); 
    private IEnumerator<MeasurementStep> steps; 

    // actually through events from device control class 
    private void MeasurementStarted() 
    { 
     // update GUI 
    } 

    // actually through events from device control class 
    private void MeasurementFinished() 
    { 
     // store measurement data 
     // update GUI 
     BeginNextMeasurementStep(); 
    } 

    private void MeasurementForm_Shown(object sender, EventArgs e) 
    { 
     steps = msg.GenerateSteps().GetEnumerator(); 
     BeginNextMeasurementStep(); 
    }   
    ... 
    ... 

    private void BeginNextMeasurementStep() 
    { 
     steps.MoveNext(); 
     if (steps.Current != null) 
     { 
      MeasurementStarted(); 
      MeasureAtPosition(steps.Current.Position); 
     } 
     else  
     { 
      // finished, update GUI 
     } 
    } 

    // stub method for device control (actually in seperate class) 
    public void MeasureAtPosition(decimal position) 
    { 
     // simulate polling 
     var context = TaskScheduler.FromCurrentSynchronizationContext(); 
     Task task = Task.Factory.StartNew(() => 
     { 
      Thread.Sleep(sleepTime); 
     }, TaskCreationOptions.LongRunning) 
     .ContinueWith(_ => 
     { 
      MeasurementFinished(); 
     }, context); 
    } 
} 

Yo esperaría que la tarea se ejecute el comando Thread.Sleep en un subproceso en segundo plano por lo que el control vuelve al hilo principal de inmediato y la interfaz gráfica de usuario no se bloquean. Pero la GUI aún se bloquea. Es como si la tarea se ejecutara en el hilo principal. ¿Alguna idea sobre lo que estoy haciendo mal aquí?

Gracias

+0

He probado esto con algún algoritmo pesado en lugar de Thread.Sleep y todo funciona bien entonces. ¿Thread.Sleep siempre funciona en el hilo principal? Esperaría que funcione en el hilo actual. – Stefan

+0

Quiero decir que esperaría que bloqueara el hilo de la tarea, no el hilo de la interfaz de usuario. – Stefan

+0

Descubrí que la ejecución de tareas en serie con ContinueWith() bloquea la UI, ya sea que use un algoritmo pesado o Thread.Sleep(). No hace esto para el algoritmo pesado cuando ejecuta tareas paralelas, pero no puedo hacer eso, mis tareas tienen que ejecutarse en orden. – Stefan

Respuesta

13

Debido a que su tarea de continuación (a través de ContinueWith) especifica una TaskScheduler el TPL utiliza para que todas las demás tareas se inició más abajo en la pila de llamadas, independientemente de si en realidad se ha especificado la misma. En otras palabras, las llamadas a Task.Factory.StartNew que se originan en el delegado Action especificado en ContinueWith usarán automáticamente el TaskScheduler especificado de manera predeterminada.

He modificado su código para ayudarlo a visualizar mejor lo que está sucediendo.

private void BeginOperation() 
{ 
    System.Diagnostics.Trace.WriteLine("BeginOperation-top " + Thread.CurrentThread.ManagedThreadId); 
    var context = TaskScheduler.FromCurrentSynchronizationContext(); 
    Task task = Task.Factory.StartNew(() => 
    { 
     System.Diagnostics.Trace.WriteLine(" BeginOperation-StartNew-top " + Thread.CurrentThread.ManagedThreadId); 
     Thread.Sleep(5000); 
     System.Diagnostics.Trace.WriteLine(" BeginOperation-StartNew-bottom " + Thread.CurrentThread.ManagedThreadId); 
    }, TaskCreationOptions.LongRunning) 
    .ContinueWith(_ => 
    { 
     System.Diagnostics.Trace.WriteLine(" BeginOperation-ContinueWith-top " + Thread.CurrentThread.ManagedThreadId); 
     EndOperation(); 
     System.Diagnostics.Trace.WriteLine(" BeginOperation-ContinueWith-bottom " + Thread.CurrentThread.ManagedThreadId); 
    }, context); 
    System.Diagnostics.Trace.WriteLine("BeginOperation-bottom " + Thread.CurrentThread.ManagedThreadId); 
} 

private void EndOperation() 
{ 
    System.Diagnostics.Trace.WriteLine("EndOperation-top " + Thread.CurrentThread.ManagedThreadId); 
    BeginOperation(); 
    System.Diagnostics.Trace.WriteLine("EndOperation-bottom " + Thread.CurrentThread.ManagedThreadId); 
} 

I examinó el código en ContinueWith a través del reflector y puedo confirmar que está intentando descubrir el contexto de ejecución utilizado por la persona que llama. Sí, lo creas o no, y a pesar de tu intuición natural de lo contrario, eso es exactamente lo que está haciendo.

Una mejor solución sería probablemente tener un hilo dedicado para realizar el sondeo de hardware.

+0

¡Whoa Brian, tú ese hombre! Ah, no me di cuenta de que lo estaba haciendo recursivo de esa manera. Marcándolo como la respuesta ya que ya pudo responder por qué no funcionó (cuál fue la pregunta). Encontrar una solución sería bueno, pero ya me rendí y lo hice de otra manera, así que ahora es más una cuestión de interés :-) – Stefan

+0

¡Me parece bien! Gracias por mirar la respuesta :) Editar: Aparentemente tengo que esperar 8 horas para premiarte con la recompensa, je. –

+0

@Daniel: Honestamente, no me importa la recompensa. Ojalá hubiera una manera de que puedas quedártelo, pero si leo las reglas correctamente, el sistema te atracará 250 sin importar nada. Lo siento :( –

12

Brian Gideon tiene razón en cuanto a la causa del problema: las tareas creadas recursivamente están comenzando con su planificador de tareas actual establecido en SynchronizationContextTaskScheduler que especifica el hilo principal. Correrlos en el hilo principal obviamente no es lo que quieres.

Puede solucionar esto utilizando una de las sobrecargas para TaskFactory.StartNew que acepta un programador de tareas y lo pasa TaskScheduler.Default.

+0

Creo que tu respuesta es un poco más al grano y sigue en el clavo. El problema es el SynchronizationContextTaskScheduler. – Chad

+0

Estaba teniendo exactamente el mismo problema donde se ejecutaba una tarea LongRunning en el contexto de la interfaz de usuario, y TaskScheduler.Default lo arregló. ¡Gracias! –

Cuestiones relacionadas