2010-05-17 143 views
10

我正在編寫一個程序,它偵聽傳入的TcpClient並在到達時處理數據。 Listen()方法在組件內的單獨線程上運行,因此它需要是線程安全的。如果我在dowhile之間循環,而我在lock()聲明中,我將釋放鎖嗎?如果不是,我該如何做到這一點?如果您打破Lock()語句,會發生什麼情況?

謝謝!

(異步TCP套接字的主題任何其他意見是值得歡迎的爲好。)

private void Listen() 
{ 
    do 
    { 
     lock (_clientLock) 
     { 
      if (!_client.Connected) break; 
      lock (_stateLock) 
      { 
       if (!_listening) break; 
       if (_client.GetStream().DataAvailable) HandleData(); 
      } 
     } 
     Thread.Sleep(0); 
    } while (true); 
} 
+6

你去監獄,然後你會被釋放。 – SwDevMan81 2010-05-17 20:23:35

回答

20

是的。鎖定語句翻譯成try/finally子句。在C#4,例如,一個鎖語句像這樣:

lock(obj) 
{ 
    // body 
} 

大致翻譯(taken from Eric Lippert's blog here)到:

bool lockWasTaken = false; 
var temp = obj; 
try 
{ 
    Monitor.Enter(temp, ref lockWasTaken); 
    { 
     // body 
    } 
} 
finally 
{ 
    if (lockWasTaken) 
     Monitor.Exit(temp); 
} 

當執行離開lock {}的範圍內,底層鎖將被釋放自動。無論你如何退出範圍(break/return/etc),這都會發生,因爲對Monitor.Exit的調用在內部被包裝在try/finally的finally塊中。

+0

比我快你是:) – 2010-05-17 20:22:23

+0

這將工作相同,當在鎖語句中使用GOTO語句分支出鎖,正確嗎? – eaglei22 2018-02-02 20:53:44

1

一旦你退出lock{},它會解開你已經鎖定了(這就像在一個using語句看待)。無論你在哪裏退出(開始,結束或中間),都是你完全放棄了鎖的範圍。想想如果你在中間引發了一個異常會發生什麼。

+8

想想如果在中間引發異常會發生什麼,這非常重要。在關鍵操作過程中出現異常和意外情況會出現問題,您做的第一件事是什麼? *釋放鎖*,以便其他代碼可以進入並訪問在關鍵操作過程中導致異常的資源!事實上,在異常時釋放鎖意味着你有*更多工作要做,而不是*更少*。 – 2010-05-17 20:24:22

+0

的確如此,我並沒有這麼想。這是一個很好的觀點。我的想法是如果它沒有釋放鎖定。有一個例外,它是不可預知的,它會在堆棧中被捕獲。如果有人不小心,這個例外可能會在幾個級別上出現泡沫,因爲沒有暗示曾經使用過鎖。現在有一個例外需要處理,一個鎖定的對象會阻止「潛在」的其他成功操作的發生。 – kemiller2002 2010-05-17 21:27:42

+0

麻煩的是,無論您選擇什麼策略,都會發生可怕的事情。鎖和例外混合不佳。如果我們擁有廉價的軟件事務內存,那麼它就不是什麼問題;我們可以簡單地將內存回滾到鎖定輸入之前的狀態。但是我們的工具箱中沒有該工具。 – 2010-05-18 19:59:47

3

是的,鎖定將被釋放。您可以使用ILDASM或Reflector來查看實際生成的代碼。鎖定語句是以下代碼的簡寫(粗略)。

Monitor.Enter(_client); 
try 
{ 
    // do your stuff 

} 
finally { 
    Monitor.Exit(_client); 
} 

注意finally塊總是被執行。

+0

順便說一句 - 這是<= C#3的正確,但不完全是在C#4中發生的事情。 – 2010-05-17 20:22:21

+0

是的,Eric Lippert在C#4中記錄了這一變化。http://blogs.msdn.com/ericlippert/archive/2009/ 03/06/locks-and-exceptions-do-not-mix.aspx – Haacked 2010-05-17 20:35:09

+3

請注意,finally塊總是執行* not *。只有在控制離開try *的情況下,finally塊纔會被執行。控制可能不會離開嘗試; try塊可能包含一個無限循環。另一個線程可能會使FailFast過程失敗。管理員可能會終止進程。一個線程可能連續兩次擊中堆棧守衛頁面,將進程關閉。有人可能會拔下機器。在所有這些情況下,finally塊不會執行。 – 2010-05-19 17:30:21

1

因爲您要求其他建議......我注意到您在嵌套鎖。這本身並不一定是壞事。但是,這是我注意到的一個紅旗。如果您在代碼的另一部分以不同的順序獲取這兩個鎖,就有可能發生死鎖。我並不是說你的代碼有什麼問題。這只是別的要小心,因爲很容易出錯。

+0

感謝您的警告!我確保按照相同的順序鎖定對象,這樣它就不會死鎖。 – dlras2 2010-05-17 20:38:30

0

要回答你的問題的另一半:

異步TCP套接字的主題任何其他意見是值得歡迎的,以及

簡單地說,我不會在時尚管理這個由您原來的帖子演示。而是從System.Net.Sockets.TcpClient和System.Net.Sockets.TcpListener類尋求幫助。使用BeginAcceptSocket(...)和BeginRead(...)之類的異步調用,並允許ThreadPool完成它的工作。以這種方式組合起來真的很容易。

你應該能夠實現你而沒有編碼可怕的話「新主題」希望所有的服務器行爲:)

這裏是思想的一個基本的例子,減去正常關機的想法,異常處理ECT:

public static void Main() 
{ 
    TcpListener listener = new TcpListener(new IPEndPoint(IPAddress.Loopback, 8080)); 
    listener.Start(); 
    listener.BeginAcceptTcpClient(OnConnect, listener); 

    Console.WriteLine("Press any key to quit..."); 
    Console.ReadKey(); 
} 

static void OnConnect(IAsyncResult ar) 
{ 
    TcpListener listener = (TcpListener)ar.AsyncState; 
    new TcpReader(listener.EndAcceptTcpClient(ar)); 
    listener.BeginAcceptTcpClient(OnConnect, listener); 
} 

class TcpReader 
{ 
    string respose = "HTTP 1.1 200\r\nContent-Length:12\r\n\r\nHello World!"; 
    TcpClient client; 
    NetworkStream socket; 
    byte[] buffer; 

    public TcpReader(TcpClient client) 
    { 
     this.client = client; 
     socket = client.GetStream(); 

     buffer = new byte[1024]; 
     socket.BeginRead(buffer, 0, 1024, OnRead, socket); 
    } 

    void OnRead(IAsyncResult ar) 
    { 
     int nBytes = socket.EndRead(ar); 
     if (nBytes > 0) 
     { 
      //you have data... do something with it, http example 
      socket.BeginWrite(
       Encoding.ASCII.GetBytes(respose), 0, respose.Length, null, null); 

      socket.BeginRead(buffer, 0, 1024, OnRead, socket); 
     } 
     else 
      socket.Close(); 
    } 
} 

有關如何做到這一點看SslTunnel Library我寫了前段時間更爲複雜的例子。

相關問題