2011-06-18 28 views
16

Kodumda hatalı bir hata var. Son derece nadirdir (her birkaç haftada bir olabilir), ama orada ve neden olduğundan emin değilim.Çok iş parçacıklı .NET sıra sorunları

Biz 2 iş parçacığı çalışan var, 1 diş ağ iletileri alır ve böyle bir Queue'ya ekler:

while (NetworkingClient.DataMessages.Count > 0) 
{ 
    DataMessage message = NetworkingClient.DataMessages.Dequeue(); 

    switch (message.messageType) 
    { 
     ... 
    } 
} 
:

DataMessages.Enqueue(new DataMessage(client, msg)); 

başka iplik böyle, bu sıraya kapalı mesajlar alır ve bunları kolları

Ancak her seferinde sık sık switch (message.messageType) numaralı hat üzerinde bir NullReferenceException alıyorum ve hata ayıklayıcısında mesajın boş olduğunu görebiliyorum.

Sıfır değerinin sıraya konması mümkün değildir (kodun ilk bitine bakın) ve bunlar kuyruğu kullanan yalnızca 2 şeydir.

Queue iş parçacığı için güvenli değil, diğer iş parçacığının tam olarak anıya geçtiğini ve bu da aksamaya neden olabileceğimi düşünüyor olabilir mi?

+0

.NET BCL'deki çok az şey bu şekilde iş parçacığı için güvenlidir. Bu durumda, "Kuyruk iş parçacığı güvenliğini sağlamak için, tüm işlemler [Senkronize] tarafından döndürülen sargı aracılığıyla gerçekleştirilmelidir (http://msdn.microsoft.com/en-us/library/system.collections.queue .aspx) yöntemi. Sorununuz, eğer varsa, iki iş parçacığının aynı anda çoğaltılması veya deforme edilmesi olabilir. Her iki şekilde de, iplik güvenliği sizin sorumluluğunuzdadır. – bzlm

+0

+1, İyi soru, gerçekten bir yarış durumu ve onun sonucu (bozuk/kararsız/beklenmedik durum) gösterir. –

+0

Kodunuzun görünümü ile, iş parçacıklarınız için sadece bir saf döngü yapıyorsunuz gibi görünüyor. Bunu senkronize etmek için uygun bir [sınırlanmış arabellek] (http://en.wikipedia.org/wiki/Producer-consumer_problem) uygulanmasına dikkat etmelisiniz. –

cevap

9
while (NetworkingClient.DataMessages.Count > 0) 
    { 
     // once every two weeks a context switch happens to be here. 
     DataMessage message = NetworkingClient.DataMessages.Dequeue(); 

     switch (message.messageType) 
     { 
      ... 
     } 
    } 

... ve bu konumda o bağlam anahtarı, ilk ifadesinin (NetworkingClient.DataMessages.Count > 0) her iki iş parçacığı için de geçerlidir sonucunu ve ilk almak Dequeue() operasyona s olsun biri olsun zaman nesne var ve ikinci iş parçacığı bir null (InvalidOperationException yerine, çünkü Queue'nun iç durumu tam olarak doğru istisna atmak için güncelleştirilemedi).

  1. Kullanım NET 4,0 ConcurrentQueue

  2. planı ayrı kod:

ve böyle bir şekilde görünmesi:

Şimdi iki seçeneğiniz var

while(true) 
{ 
    DataMessage message = null; 

    lock(NetworkingClient.DataMessages.SyncRoot) { 
     if(NetworkingClient.DataMessages.Count > 0) { 
      message = NetworkingClient.DataMessages.Dequeue(); 
     } else { 
     break; 
     } 
    } 
    // .. rest of your code 
} 

Düzenleme: Heandel'in yorumunu yansıtacak şekilde güncellendi.

Eğer kesin nedene ilgilenen durumda
+2

'_sync' için' Queue' 'SyncRoot' nesnesini kullanabilirsiniz. Bu onun amacı! –

+0

Tamamen haklısınız. Teşekkürler! –

+0

soru "Başka bir iş parçacığı bu sıraya göre iletileri alır ve bunları işler" der. Bu, 2 iş parçacığının eşzamanlı bir dequeue asla gerçekleşmeyeceği anlamına gelir. #justsaying – bzlm

11

Kuyruk evreli, ben tam zamanında diğer iplik enqueuing olduğunu ve bu kusurunu, en dequeuing ediyorum olabilir değil mi?

Kesinlikle. Queue, diş güvenli değildir. Bir iş parçacığı güvenli sırasını System.Collections.Concurrent.ConcurrentQueue. Sorununuzu çözmek için kullanın.

+0

Bu ConcurrentQueue hakkında bilgi bile bilmiyordum - her zaman kullanılan kilit ... – VikciaR

+0

Bu .NET 4'te yenidir;) –

+2

'ConcurrentQueue' körü körüne kullanmayın; sadece ne yaptığını biliyorsan onu kullan. Bazı durumlarda kilitli bir 'Queue' daha iyi bir seçimdir. Eşzamanlı koleksiyon sınıfları eşzamanlılık sıkıntıları için mucize kürleri değildir :) – Timwi

7

:

Enqueue şuna benzer:

this._array[this._tail] = item; 
this._tail = (this._tail + 1) % this._array.Length; 
this._size++; 
this._version++; 

Ve Dequeue böyle:

T result = this._array[this._head]; 
this._array[this._head] = default(T); 
this._head = (this._head + 1) % this._array.Length; 
this._size--; 
this._version++; 

yarış şöyledir:

  • var kuyrukta 1 elemanının (baş == kuyruk) böylece okuyucu iplik dequeuing başlar ama
  • Sonra başka bir eleman kuyruğa olan Dequeue ilk satır sonra kesilir ve bu noktada head eşittir pozisyonda tail getirilirse .
  • Şimdi Dequeue özgeçmiş ve (davanızın null) varsayılan (T) olsun dequeue sadece default(T)
  • ile Enqueue tarafından aramak dahaki sefere takıldı eleman yerine gerçek değerini yazar
+1

+1, sahnelerin ardında neler olduğunu görmek güzel. Multithreading yaparken daha dikkatli olmalıyım. Bu hatayı bulduğuma sevindim ve onu serbest bırakmayacak. – Hannesh

İlgili konular