2010-02-06 12 views
8

Ho uno scenario in cui ho bisogno di rimuovere un articolo per la coda non appena elaborato. Capisco che non posso rimuovere un elemento da una raccolta, mentre in loop, ma chiedevo se qualcosa potrebbe essere fatto con l'enumeratore ecc ...Come posso modificare una collezione di code in un ciclo?

Questo è solo un esempio di base che getta un errore "Collection è stato modificato dopo la l'enumeratore è stato istanziato. "

Qualche suggerimento? Molte grazie!!!

codice è il seguente:

 class Program 
     { 
      static void Main() 
      { 

       Queue<Order> queueList = GetQueueList(); 

       foreach (Order orderItem in queueList) 
       { 
        Save(orderItem); 
        Console.WriteLine("Id :{0} Name {1} ", orderItem.Id, orderItem.Name); 
        queueList.Dequeue(); 
       } 
       Console.Read(); 

      } 

      private static void Save(Order orderItem) 
      { 
       //we are pretending to save or do something. 
      } 

      private static Queue<Order>GetQueueList() 
      { 
       Queue<Order> orderQueue = new Queue<Order>(); 
       orderQueue.Enqueue(new Order { Id = 1, Name = "Order 1" }); 
       orderQueue.Enqueue(new Order { Id = 1, Name = "Order 2" }); 
       orderQueue.Enqueue(new Order { Id = 2, Name = "Order 3" }); 
       orderQueue.Enqueue(new Order { Id = 3, Name = "Order 4" }); 
       orderQueue.Enqueue(new Order { Id = 4, Name = "Order 5" }); 
       return orderQueue; 
      } 
     } 

     public class Order 
     { 
      public int Id { get; set; } 
      public string Name { get; set; } 
     } 

risposta

10

Cambia il tuo foreach a:

while (queueList.Count > 0) 
{ 
    Order orderItem = queueList.Dequeue(); 
    Save(orderItem); 
    Console.WriteLine("Id :{0} Name {1} ", orderItem.Id, orderItem.Name); 
} 

Edit:

rielaborare se salvataggio non riesce fare qualcosa di simile:

while (queueList.Count > 0) 
{ 
    Order orderItem = queueList.Dequeue(); 

    if (!Save(orderItem)) 
    { 
     queueList.Enqueue(orderItem); // Reprocess the failed save, probably want more logic to prevent infinite loop 
    } 
    else 
    { 
     Console.WriteLine("Successfully saved: {0} Name {1} ", orderItem.Id, orderItem.Name); 
    } 
} 

Edit:

John K cita filo di sicurezza che è una preoccupazione valida se si dispone di più thread accedono allo stesso Queue. Vedere http://ccutilities.codeplex.com/SourceControl/changeset/view/40529#678487 per una classe ThreadSafeQueue che copre problemi di sicurezza del thread semplice.


Edit: Ecco l'esempio della sicurezza filo continuo che punta a tutti di :-)

Ecco un esempio dei problemi di sicurezza filo menzionati. Come mostrato, il valore predefinito di Queue può "mancare" elementi mentre si riduce ancora il conteggio.

Aggiornamento: Per rappresentare meglio il problema. Non aggiungo mai un oggetto nullo allo Queue ma lo standard Queue.Dequeue() restituisce diversi valori nulli. Questo da solo andrebbe bene, ma in tal modo un elemento valido viene rimosso dalla raccolta interna e il Count diminuisce. È un presupposto sicuro, in questo esempio specifico, che ogni elemento null restituito da un'operazione Queue.Dequeue() rappresenta un elemento valido che non è mai stato elaborato.

using System; 
using System.Collections.Generic; 
using System.Threading; 

namespace SO_ThreadSafeQueue 
{ 
    class Program 
    { 
     static int _QueueExceptions; 
     static int _QueueNull; 
     static int _QueueProcessed; 

     static int _ThreadSafeQueueExceptions; 
     static int _ThreadSafeQueueNull; 
     static int _ThreadSafeQueueProcessed; 

     static readonly Queue<Guid?> _Queue = new Queue<Guid?>(); 
     static readonly ThreadSafeQueue<Guid?> _ThreadSafeQueue = new ThreadSafeQueue<Guid?>(); 
     static readonly Random _Random = new Random(); 

     const int Expected = 10000000; 

     static void Main() 
     { 
      Console.Clear(); 
      Console.SetCursorPosition(0, 0); 
      Console.WriteLine("Creating queues..."); 

      for (int i = 0; i < Expected; i++) 
      { 
       Guid guid = Guid.NewGuid(); 
       _Queue.Enqueue(guid); 
       _ThreadSafeQueue.Enqueue(guid); 
      } 

      Console.SetCursorPosition(0, 0); 
      Console.WriteLine("Processing queues..."); 

      for (int i = 0; i < 100; i++) 
      { 
       ThreadPool.QueueUserWorkItem(ProcessQueue); 
       ThreadPool.QueueUserWorkItem(ProcessThreadSafeQueue); 
      } 

      int progress = 0; 

      while (_Queue.Count > 0 || _ThreadSafeQueue.Count > 0) 
      { 
       Console.SetCursorPosition(0, 0); 

       switch (progress) 
       { 
        case 0: 
         { 
          Console.WriteLine("Processing queues... |"); 
          progress = 1; 
          break; 
         } 
        case 1: 
         { 
          Console.WriteLine("Processing queues... /"); 
          progress = 2; 
          break; 
         } 
        case 2: 
         { 
          Console.WriteLine("Processing queues... -"); 
          progress = 3; 
          break; 
         } 
        case 3: 
         { 
          Console.WriteLine("Processing queues... \\"); 
          progress = 0; 
          break; 
         } 
       } 

       Thread.Sleep(200); 
      } 

      Console.SetCursorPosition(0, 0); 
      Console.WriteLine("Finished processing queues..."); 
      Console.WriteLine("\r\nQueue Count:   {0} Processed: {1, " + Expected.ToString().Length + "} Exceptions: {2,4} Null: {3}", _Queue.Count, _QueueProcessed, _QueueExceptions, _QueueNull); 
      Console.WriteLine("ThreadSafeQueue Count: {0} Processed: {1, " + Expected.ToString().Length + "} Exceptions: {2,4} Null: {3}", _ThreadSafeQueue.Count, _ThreadSafeQueueProcessed, _ThreadSafeQueueExceptions, _ThreadSafeQueueNull); 

      Console.WriteLine("\r\nPress any key..."); 
      Console.ReadKey(); 
     } 

     static void ProcessQueue(object nothing) 
     { 
      while (_Queue.Count > 0) 
      { 
       Guid? currentItem = null; 

       try 
       { 
        currentItem = _Queue.Dequeue(); 
       } 
       catch (Exception) 
       { 
        Interlocked.Increment(ref _QueueExceptions); 
       } 

       if (currentItem != null) 
       { 
        Interlocked.Increment(ref _QueueProcessed); 
       } 
       else 
       { 
        Interlocked.Increment(ref _QueueNull); 
       } 

       Thread.Sleep(_Random.Next(1, 10)); // Simulate different workload times 
      } 
     } 

     static void ProcessThreadSafeQueue(object nothing) 
     { 
      while (_ThreadSafeQueue.Count > 0) 
      { 
       Guid? currentItem = null; 

       try 
       { 
        currentItem = _ThreadSafeQueue.Dequeue(); 
       } 
       catch (Exception) 
       { 
        Interlocked.Increment(ref _ThreadSafeQueueExceptions); 
       } 

       if (currentItem != null) 
       { 
        Interlocked.Increment(ref _ThreadSafeQueueProcessed); 
       } 
       else 
       { 
        Interlocked.Increment(ref _ThreadSafeQueueNull); 
       } 

       Thread.Sleep(_Random.Next(1, 10)); // Simulate different workload times 
      } 
     } 

     /// <summary> 
     /// Represents a thread safe <see cref="Queue{T}"/> 
     /// </summary> 
     /// <typeparam name="T"></typeparam> 
     public class ThreadSafeQueue<T> : Queue<T> 
     { 
      #region Private Fields 
      private readonly object _LockObject = new object(); 
      #endregion 

      #region Public Properties 
      /// <summary> 
      /// Gets the number of elements contained in the <see cref="ThreadSafeQueue{T}"/> 
      /// </summary> 
      public new int Count 
      { 
       get 
       { 
        int returnValue; 

        lock (_LockObject) 
        { 
         returnValue = base.Count; 
        } 

        return returnValue; 
       } 
      } 
      #endregion 

      #region Public Methods 
      /// <summary> 
      /// Removes all objects from the <see cref="ThreadSafeQueue{T}"/> 
      /// </summary> 
      public new void Clear() 
      { 
       lock (_LockObject) 
       { 
        base.Clear(); 
       } 
      } 

      /// <summary> 
      /// Removes and returns the object at the beggining of the <see cref="ThreadSafeQueue{T}"/> 
      /// </summary> 
      /// <returns></returns> 
      public new T Dequeue() 
      { 
       T returnValue; 

       lock (_LockObject) 
       { 
        returnValue = base.Dequeue(); 
       } 

       return returnValue; 
      } 

      /// <summary> 
      /// Adds an object to the end of the <see cref="ThreadSafeQueue{T}"/> 
      /// </summary> 
      /// <param name="item">The object to add to the <see cref="ThreadSafeQueue{T}"/></param> 
      public new void Enqueue(T item) 
      { 
       lock (_LockObject) 
       { 
        base.Enqueue(item); 
       } 
      } 

      /// <summary> 
      /// Set the capacity to the actual number of elements in the <see cref="ThreadSafeQueue{T}"/>, if that number is less than 90 percent of current capactity. 
      /// </summary> 
      public new void TrimExcess() 
      { 
       lock (_LockObject) 
       { 
        base.TrimExcess(); 
       } 
      } 
      #endregion 
     } 

    } 
} 
+0

Ciao. Funziona. Che dire se volevo disconnettere l'elemento solo se il salvataggio era riuscito. Posso ancora farlo? sorry + grazie non familiare con la coda – user9969

+1

@ devnet247: non proprio. Se non rimuovi l'elemento in cima, non puoi prendere quello dopo. Devi spostare l'articolo fallito nella coda della coda come fa questo esempio. –

+0

Grazie mille per il vostro aiuto. Questo lo farà per ora. Devo implementare il threading completo e questo processo si verifica in un servizio wcf. Un'altra cosa da imparare.grazie ancora – user9969

0

A me, sembra che si sta tentando di elaborare l'elemento in coda di uno ad uno.

Come avvolgere questo nel ciclo while ed elaborare ogni elemento da Dequeue, finché la coda non è vuota?

+0

Nel codice di produzione abbiamo una coda di ordini da elaborare e dopo ognuno di essi ho bisogno di deselezionarli. Puoi mostrarmi con un piccolo frammento cosa intendi? Grazie – user9969

+0

@ devnet247: vedere la mia risposta per uno snippet di codice. –

1

foreach come un modo ragionevole per iterare attraverso la coda quando non si sta rimuovendo elementi

Quando si vuole rimuovere e gli elementi di processo, il modo corretto thread-safe è solo rimuoverle uno alla tempo ed elaborarli dopo che sono stati rimossi.

Un modo è questo

// the non-thread safe way 
// 
while (queueList.Count > 0) 
{ 
    Order orderItem = queueList.Dequeue(); 
    Save(orderItem); 
    Console.WriteLine("Id :{0} Name {1} ", orderItem.Id, orderItem.Name); 
} 

E 'possibile che il numero di elementi nella coda per passare da queueList.Count e queueList.Dequeue(), in modo da essere thread-safe, si deve utilizzare solo Dequeue, ma Dequeue genererà quando la coda è vuota, quindi devi usare un gestore di eccezioni.

// the thread safe way. 
// 
while (true) 
{ 
    Order orderItem = NULL; 
    try { orderItem = queueList.Dequeue(); } catch { break; } 
    if (null != OrderItem) 
    { 
     Save(orderItem); 
     Console.WriteLine("Id :{0} Name {1} ", orderItem.Id, orderItem.Name); 
    } 
} 
+0

Il tentativo di aggirare il problema non risolverà il problema di sicurezza del thread, poiché con molti thread che colpiscono la stessa coda è possibile che Dequeue() restituisca un oggetto nullo mentre rimuove effettivamente un oggetto dalla raccolta interna. Vedi http://ccutilities.codeplex.com/SourceControl/changeset/view/40529#678487 per un'opzione migliore. –

+0

@Cory: grazie. hai un riferimento su quel comportamento? –

+0

@ John: No solo esperienza. Recentemente ho scritto un'applicazione che generava 100 thread per elaborare i file da un singolo 'Queue' e ho notato che anche se il mio' Queue.Count' è andato a zero altri contatori "elaborati" non sono stati aggiunti al 'Queue.Count iniziale '. I semplici lock che ho aggiunto a 'ThreadSafeQueue' hanno fornito risultati coerenti, non importa quanti thread ho lanciato allo stesso' Queue' –

Problemi correlati