2013-04-01 15 views
5

Ho un codice che scorre un elenco di record, avvia un'attività di esportazione per ciascuno e aumenta un contatore di avanzamento di 1 ogni volta che un'attività termina, in modo che l'utente sappia quanto è lungo il processo.Come posso tenere traccia di quante attività asincrone sono state completate in un ciclo?

Ma a seconda del tempo dei miei loop, vedo spesso l'output che mostra un numero più alto prima di un numero inferiore.

Per esempio, mi sarei aspettato di vedere l'uscita in questo modo:

 
Exporting A 
Exporting B 
Exporting C 
Exporting D 
Exporting E 
Finished 1/5 
Finished 2/5 
Finished 3/5 
Finished 4/5 
Finished 5/5 

Ma invece io ottenere output come questo

 
Exporting A 
Exporting B 
Exporting C 
Exporting D 
Exporting E 
Finished 1/5 
Finished 2/5 
Finished 5/5 
Finished 4/5 
Finished 3/5 

Non mi aspetto che l'uscita per l'esattezza da quando ho' m non blocca il valore quando lo aggiorno/utilizzo (a volte emette lo stesso numero due volte o salta un numero), tuttavia non mi aspetto che vada indietro.

Il mio set di dati di test è di 72 valori, e il relativo codice è simile al seguente:

var tasks = new List<Task>(); 
int counter = 0; 

StatusMessage = string.Format("Exporting 0/{0}", count); 

foreach (var value in myValues) 
{ 
    var valueParam = value; 

    // Create async task, start it, and store the task in a list 
    // so we can wait for all tasks to finish at the end 
    tasks.Add(
     Task.Factory.StartNew(() => 
     { 
      Debug.WriteLine("Exporting " + valueParam); 

      System.Threading.Thread.Sleep(500); 
      counter++; 
      StatusMessage = string.Format("Exporting {0}/{1}", counter, count); 

      Debug.WriteLine("Finished " + counter.ToString()); 
     }) 
    ); 
} 

// Begin async task to wait for all tasks to finish and update output 
Task.Factory.StartNew(() => 
{ 
    Task.WaitAll(tasks.ToArray()); 
    StatusMessage = "Finished"; 
}); 

L'uscita può apparire invertiti in entrambe le istruzioni di debug e il StatusMessage uscita.

Qual è il modo corretto per tenere il conto di quante attività asincrone in un ciclo sono state completate in modo che questo problema non si verifichi?

+1

Sei sicuro che il 'Async' Prcess è garantito per l'uscita in un' Ascendente Order' Sulla base di quello che ho sto vedendo che non penso che lo farà a meno che tu non faccia un 'Sort' sulla' Lista ' – MethodMan

+0

Dato che stai iniziando tutte le attività essenzialmente nello stesso tempo, finiranno in un ordine casuale. Anche se usi Interlocked.Increment "Finished n/5" non sarà monotona. – Phil

+0

@DJKRAZE La variabile 'counter' non dovrebbe essere aumentata fino al termine dell'esportazione (sostituito da' Thread.Sleep' a scopo di test). Differenti esportazioni richiedono tempi di completamento diversi a seconda della quantità di dati che contengono, quindi non desidero il valore predefinito all'inizio dell'attività asincrona. Invece, sto tentando di utilizzare una variabile condivisa che viene aumentata di 1 ogni volta che viene completata un'attività di esportazione. – Rachel

risposta

7

Si ottiene uscita miscelata, perché contatore non viene incrementato nello stesso ordine Debug.WriteLine(...) metodo è eseguito.

Per ottenere una relazione coerente, è possibile introdurre un blocco di segnalazione nel compito

tasks.Add(
    Task.Factory.StartNew(() => 
    { 
     Debug.WriteLine("Exporting " + valueParam); 

     System.Threading.Thread.Sleep(500); 
     lock(progressReportLock) 
     { 
      counter++; 
      StatusMessage = string.Format("Exporting {0}/{1}", counter, count); 
      Debug.WriteLine("Finished " + counter.ToString()); 
     } 
    }) 
); 
+0

@svick Yikes, hai perfettamente ragione. – IdeaHat

+0

Ho il sospetto che questo potrebbe essere stato il caso, grazie. Ho eseguito alcuni test usando un 'lock' e sembra che stia uscendo correttamente ora – Rachel

5

In questo esempio la variabile counter rappresenta lo stato condiviso tra più thread. L'utilizzo dell'operatore ++ sullo stato condiviso non è sicuro e fornirà risultati non corretti. Si riduce essenzialmente le seguenti istruzioni

  1. contatore spinta per impilare
  2. spinta 1 impilare
  3. aggiungere valori in pila
  4. deposito nel contatore

Poiché più thread sono in esecuzione questa affermazione è possibile per uno di interrompere l'altra parte completando la sequenza di cui sopra. Ciò farebbe sì che il valore errato finisse in counter.

Invece di ++ usa la seguente istruzione

Interlocked.Increment(ref counter); 

Questa operazione è specificamente progettato per aggiornare lo stato che può essere condiviso tra più thread. L'interblocco avverrà atomicamente e non risentirà delle condizioni di gara che ho delineato

L'effettiva visualizzazione dei valori fuori uso soffre di un problema simile anche dopo la mia soluzione suggerita. L'operazione di incremento e visualizzazione non è atomica e quindi un thread può interrompere l'altro tra l'incremento e la visualizzazione. Se si desidera che le operazioni siano interrompibili da altri thread, sarà necessario utilizzare un blocco.

object lockTarget = new object(); 
int counter = 0; 

... 

lock (lockTarget) { 
    counter++; 
    StatusMessage = string.Format("Exporting {0}/{1}", counter, count); 
    Debug.WriteLine("Finished " + counter.ToString()); 
} 

noti che poiché l'incremento di counter ora avviene all'interno della serratura non v'è la necessità di utilizzare Interlocked.Increment

+2

Sembra che si utilizzi Interlocked.Increment non sarebbe di aiuto con i rapporti di stato fuori-ordine che ha ottenuto. – alex

+0

@alex è corretto, utilizzando 'Interlocked.Increment' risulta ancora nell'output nell'ordine errato – Rachel

+0

@alex aggiornato per correggere quella parte – JaredPar

Problemi correlati