2011-06-18 31 views
16

Tôi gặp lỗi nghiêm trọng trong mã của mình. Nó cực kỳ hiếm (xảy ra một vài tuần một lần), nhưng nó ở đó và tôi không chắc tại sao.Các vấn đề về hàng đợi .NET đa luồng

Chúng tôi có 2 đề chạy, 1 thread được thông điệp mạng và thêm chúng vào một Queue như thế này:

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

chủ đề Một mất thông điệp tắt hàng đợi này và xử lý chúng, như thế này:

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

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

Tuy nhiên một lần như vậy thường tôi nhận được một NullReferenceException trên dòng switch (message.messageType) và tôi có thể nhìn thấy trong trình gỡ lỗi thông báo là null.

Không thể đặt giá trị rỗng vào hàng đợi (xem bit đầu tiên của mã) và đây là 2 thứ duy nhất sử dụng hàng đợi.

Hàng đợi không an toàn theo chủ đề, có thể là tôi đang giảm tốc tại thời điểm chính xác mà luồng khác đang đẩy và điều này gây ra trục trặc?

+0

rất ít những thứ trong .NET BCL là thread-safe trong thời trang này. Trong trường hợp này, "để đảm bảo an toàn luồng của Hàng đợi, tất cả các thao tác phải được thực hiện thông qua trình bao bọc được trả về bởi [Đồng bộ hoá] (http://msdn.microsoft.com/en-us/library/system.collections.queue .aspx) phương pháp ". Vấn đề của bạn cũng có thể là hai chủ đề đang enqueueing hoặc dequeueing đồng thời, nếu có. Dù bằng cách nào, chủ đề an toàn là trách nhiệm của bạn. – bzlm

+0

+1, Câu hỏi hay, nó thực sự chứng minh một điều kiện chủng tộc và kết quả của nó (tình huống hư hỏng/không ổn định/không mong muốn). –

+0

Bằng giao diện mã của bạn, có vẻ như bạn đang thực hiện một vòng lặp ngây thơ cho các chuỗi của mình. Bạn nên xem xét việc triển khai một [bộ đệm bị chặn] thích hợp (http://en.wikipedia.org/wiki/Producer-consumer_problem) để đồng bộ hóa điều này. –

Trả lời

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) 
     { 
      ... 
     } 
    } 

... và khi bạn nhận được rằng bối cảnh chuyển đổi trong vị trí đó, kết quả của biểu thức đầu tiên (NetworkingClient.DataMessages.Count > 0) là đúng cho cả hai chủ đề, và một trong đó có được để hoạt động Dequeue() đầu tiên có được là đối tượng và chuỗi thứ hai nhận được một null (thay vì InvalidOperationException vì trạng thái bên trong của Queue không được cập nhật đầy đủ để ném ngoại lệ đúng).

Bây giờ bạn có hai lựa chọn:

  1. Sử dụng .NET 4,0 ConcurrentQueue

  2. Refactor mã của bạn:

và làm cho nó trông bằng cách nào đó như thế này:

while(true) 
{ 
    DataMessage message = null; 

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

Chỉnh sửa: cập nhật để phản ánh bình luận của Heandel.

+2

Bạn có thể sử dụng đối tượng 'SyncRay' của đối tượng' SyncRoot' cho '_sync'. Đó là mục đích của nó! –

+0

Bạn hoàn toàn đúng. Cảm ơn! –

+0

câu hỏi có nghĩa là "Một chuỗi khác nhận thư từ hàng đợi này và xử lý chúng". Điều này có nghĩa là một dequeue đồng thời từ 2 chủ đề sẽ không bao giờ xảy ra. #justsaying – bzlm

11

là Queue không thread-safe, có thể đó là rằng tôi dequeuing tại thời điểm chính xác rằng các chủ đề khác được enqueuing và điều này gây ra trục trặc?

Chính xác. Queue không an toàn chỉ. Hàng đợi an toàn theo luồng là System.Collections.Concurrent.ConcurrentQueue. Sử dụng nó thay vào đó để khắc phục sự cố của bạn.

+0

Thậm chí không biết về ConcurrentQueue này - allways được sử dụng khóa ... – VikciaR

+0

Nó là mới trong .NET 4;) –

+2

Không sử dụng 'ConcurrentQueue' một cách mù quáng; chỉ sử dụng nó nếu bạn biết bạn đang làm gì. Trong một số trường hợp, một 'Queue' có khóa là một lựa chọn tốt hơn. Các lớp sưu tập đồng thời không phải là các phép điều trị phép lạ cho các tai ương đồng thời :) – Timwi

7

Trong trường hợp bạn quan tâm đến lý do chính xác:

Enqueue trông như thế này:

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

Dequeue như thế này:

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

Cuộc đua diễn ra như sau:

  • Có 1 phần tử trong hàng đợi (đầu == đuôi) sao cho chuỗi người đọc của bạn bắt đầu khử nhưng bị gián đoạn sau dòng đầu tiên trong Dequeue
  • Sau đó, một phần tử khác được đặt vào vị trí tail bằng head tại thời điểm này .
  • Bây giờ Dequeue sơ yếu lý lịch và ghi đè các yếu tố được chỉ chèn vào bởi Enqueue với default(T)
  • Lần sau khi bạn gọi dequeue bạn sẽ có được mặc định (T) (trong trường hợp rỗng của bạn) thay vì giá trị thực tế
+1

+1, thật tuyệt khi thấy chính xác những gì đang diễn ra đằng sau hậu trường. Tôi đoán tôi nên cẩn thận hơn khi đa luồng! Tôi rất vui vì tôi đã tìm thấy lỗi này và nó sẽ không biến nó thành bản phát hành. – Hannesh

Các vấn đề liên quan