2016-01-21 61 views
1

給定下面的簡單套接字客戶端類,將其連接到TCP服務器(我使用SocketTest3,可以在線免費獲得)。然後斷開服務器並等待一會兒。你應該得到一個LockRecursionExceptionReaderWriterLockSlim使用Socket.BeginReceive拋出LockRecursionException

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 

using System.Net; 
using System.Net.Sockets; 

using System.Threading; 

namespace SocketRwlTest 
{ 
    public class SocketRwlTest 
    { 
     private Socket client = new Socket(AddressFamily.InterNetwork, 
              SocketType.Stream, 
              ProtocolType.Tcp); 
     private readonly ReaderWriterLockSlim rwl = new ReaderWriterLockSlim(); 
     private const int maxLength = 200; 

     public SocketRwlTest(IPAddress address, ushort port) 
     { 
      client.Connect(new IPEndPoint(address, port)); 
      ReceiveOne(); 
     } 

     private void ReceiveOne() 
     { 
      rwl.EnterReadLock(); 
      try 
      { 
       var inArray = new byte[maxLength]; 
       client.BeginReceive(inArray, 0, maxLength, 0, 
            new AsyncCallback(ReceivedCallback), 
            inArray); 
      } 
      finally 
      { 
       rwl.ExitReadLock(); 
      } 
     } 

     private void ReceivedCallback(IAsyncResult ar) 
     { 
      client.EndReceive(ar); 
      ReceiveOne(); 
     } 
    } 
} 

我不明白爲什麼它發生在給出的簡化示例中。我知道,只要我收到一個零長度的消息,我就應該停止呼叫ReceiveOne,但這更多的是練習。我想知道如果一個類似的bug能夠保持在後臺運行的不斷的回調流,並且在沒有明顯不好的事情發生的情況下竊取資源。我必須承認我並不期待這個特別的例外。

問題1:爲什麼會發生這種情況? BeginXYZ方法可能允許立即在同一個線程上執行回調?如果是這樣的話,誰會說在正常的運行時期間不會發生這種情況?

問題2:在這種情況下是否有避免獲取此異常的方法,同時仍保持「期望」行爲?我的意思是發起一個不間斷的回調流。

我使用Visual Studio 2010和.NET 4

+0

我想你錯過了一個'客戶端的調用。EndReceive'在你的'ReceiveCallback'方法中(雖然這不能回答這個問題) –

+2

The [reference source](http://referencesource.microsoft.com/#System/net/System/Net/Sockets/Socket.cs, 12174aa527fd9499)肯定表明BeginReceive被設計爲可能在單個線程上執行:「我們啓動接收,如果它同步完成,我們會調用回調函數,否則我們將返回一個IASyncResult,調用者可以使用它來等待根據需要打開或檢索最終狀態。「我沒有在文檔或源代碼中看到任何讓我認爲BeginReceive被* required *在另一個線程上調用回調的源代碼。 –

+0

我沒有意識到這一點。實際上,它非常有意義。性能明智的。所以這基本上意味着當回調調用BeginReceive時,您不能使用非遞歸鎖。無論如何,這是一種遞歸,所以它是有道理的。 :) –

回答

1

問題1:爲什麼會出現這種情況? BeginXYZ方法可能允許立即在同一個線程上執行回調?如果是這樣的話,誰會說在正常的運行時期間不會發生這種情況?

如上所述by mike z in the comments,所述BeginReceive()方法沒有需要異步執行。如果數據可用,則同步執行,並在同一個線程中調用回調委託。這是定義爲遞歸調用,因此不會與使用非遞歸的鎖對象(例如您在此使用的ReaderWriterLockSlim)兼容。

這肯定會發生「正常運行時」。我不確定我是否理解你問題的第二部分。誰說這不可能發生?沒有人。它可以發生。

問題2:在這種情況下是否有避免獲取此異常的方法,同時仍然保持「所需」行爲?我的意思是發起一個不間斷的回調流。

恐怕我也不知道你的意思是「發出一個不停止的回調流」。

一個明顯的解決方法是通過將LockRecursionPolicy.SupportsRecursion傳遞給它的構造函數來啓用ReaderWriterLockSlim對象上的遞歸。或者,您可以在嘗試鎖定之前檢查IsReadLockHeld屬性。

從您的代碼示例中不清楚爲什麼您有鎖,不必介意爲什麼它以特定方式使用。當您撥打BeginReceive()時,正確的解決方案可能不會保持鎖定狀態。僅在處理EndReceive()的結果時使用它。

相關問題