2010-07-30 8 views
16

Attualmente sto scrivendo un'applicazione che controllerà il posizionamento di un dispositivo di misurazione. A causa dell'hardware coinvolto ho bisogno di eseguire il polling per il valore di posizione corrente costantemente durante l'esecuzione del motore elettrico. Sto cercando di creare la classe responsabile di questo in modo che esegua il polling su un thread in background e genererà un evento quando viene raggiunta la posizione desiderata. L'idea è che il sondaggio non bloccherà il resto dell'applicazione o la GUI. Volevo utilizzare la nuova classe Threading.Task.Task per gestire tutti gli impianti idraulici di thread in background per me..Net perché Threading.Task.Task blocca ancora la mia UI?

Non ho ancora l'hardware, ma ho creato uno stub di prova per simulare questo comportamento. Ma quando eseguo l'applicazione in questo modo, la GUI continua a bloccarsi. Vedere un esempio semplificato del codice seguente (non completo e non utilizzando una classe separata per il controllo del dispositivo). Il codice ha una sequenza di passaggi di misurazione, l'applicazione deve posizionare e quindi misurare per ogni passaggio.

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); 
    } 
} 

mi si aspetterebbe il compito di eseguire il comando Thread.Sleep su un thread in background in modo da controllo ritorna al thread principale immediatamente e l'interfaccia grafica non si bloccano. Ma la GUI viene ancora bloccata. È come l'attività viene eseguita sul thread principale. Qualche idea su cosa sto facendo male qui?

Grazie

+0

Ho provato questo con un algoritmo pesante invece di Thread.Sleep e tutto funziona correttamente. Thread.Sleep funziona sempre sul thread principale? Mi aspetterei che funzioni nel thread corrente. – Stefan

+0

Voglio dire che mi aspetterei che blocchi il thread dell'attività, non il thread dell'interfaccia utente. – Stefan

+0

Ho scoperto che l'esecuzione di Task in serie con ContinueWith() blocca l'interfaccia utente, indipendentemente dal fatto che io usi un algoritmo pesante o Thread.Sleep(). Non lo fa per l'algoritmo pesante quando esegui attività parallele, ma non posso farlo, i miei compiti devono essere eseguiti in ordine. – Stefan

risposta

13

Perché il vostro compito continuazione (via ContinueWith) specifica un TaskScheduler TPL usa che per tutti gli altri compiti ha dato il via, più in basso lo stack di chiamate indipendentemente dal fatto che in realtà specificato esso. In altre parole, le chiamate a Task.Factory.StartNew provenienti dal delegato Action specificato in ContinueWith utilizzeranno automaticamente lo TaskScheduler specificato per impostazione predefinita.

Ho modificato il codice per aiutarvi a visualizzare meglio cosa sta succedendo.

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); 
} 

Ho esaminato il codice in ContinueWith via Reflector e posso confermare che sta tentando di scoprire il contesto di esecuzione utilizzato dal chiamante. Sì, che ci crediate o no, e nonostante la vostra naturale intuizione al contrario, questo è esattamente ciò che sta facendo.

Una soluzione migliore sarebbe probabilmente avere un thread dedicato per eseguire il polling dell'hardware.

+0

Whoa Brian, amico! Ah, non mi rendevo conto che lo stavo facendo in modo ricorsivo in quel modo. Contrassegnalo come la risposta dato che eri già in grado di rispondere perché non ha funzionato (che era la domanda). Trovare una soluzione sarebbe bello, ma ho già rinunciato e l'ho fatto in un altro modo quindi è più una questione di interesse ora :-) – Stefan

+0

Mi sembra buono! Grazie per aver esaminato la risposta :) Modifica: A quanto pare ho bisogno di aspettare 8 ore per assegnarti la taglia, heh. –

+0

@Daniel: Onestamente, mi potrebbe importare di meno della taglia. Vorrei che ci fosse un modo per tenerlo per te, ma se leggo correttamente le regole il sistema ti attaccherà 250, non importa cosa. Spiacente :( –

12

Brian Gideon è corretto sulla causa del problema - le attività create in modo ricorsivo iniziano con il loro attuale programmatore di attività impostato su SynchronizationContextTaskScheduler che specifica il thread principale. Eseguirli nel thread principale non è ovviamente quello che vuoi.

È possibile risolvere questo problema utilizzando uno dei sovraccarichi per TaskFactory.StartNew che accetta un pianificatore di attività e lo passa TaskScheduler.Default.

+0

Penso che la tua risposta sia un po 'più puntuale e ancora visibile. il SynchronizationContextTaskScheduler. – Chad

+0

Avevo lo stesso identico problema in cui era in esecuzione un'attività LongRunning nel contesto dell'interfaccia utente e TaskScheduler.Default l'ha risolto. –

Problemi correlati