2011-05-30 415 views
2

在我的ReceiveCallBack中是否有一個鎖定套接字的異步套接字?我在問,因爲另一個線程可能同時在套接字上發送數據。跨主題鎖定的套接字

private void ReceiveCallback(IAsyncResult ar) 
{ 
StateObject state = (StateObject)ar.AsyncState; 
Socket client = state.workSocket; 

lock(client) 
{ 
    int bytesRead = client.EndReceive(ar); 
    // do some work 

    // Kick off socket to receive async again. 
    client.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, 
      new AsyncCallback(ReceiveCallback), state); 
} 
}  

// This is commonly called by another thread 
public void SendMessage(string cmdName, Object data) 
{ 
    lock (client) 
    { 
     client.Send(arrayofdata, 0, arraylength, 0); 
    } 
} 
+0

這個問題是基於你可以鎖定對象以使代碼線程安全的錯覺。你不能,你只能阻止代碼,阻止它同時使用共享對象。鎖語句僅使用一個對象來存儲狀態。這應該永遠不會是一個套接字。 – 2011-05-30 20:50:59

回答

4

如果你想使它線程安全的,並能夠同時發送和接收,你需要創建兩個鎖同步對象:

private readonly object sendSyncRoot = new object(); 
private readonly object receiveSyncRoot = new object(); 

private void ReceiveCallback(IAsyncResult ar) 
{ 
    StateObject state = (StateObject)ar.AsyncState; 
    Socket client = state.workSocket; 

    lock (receiveSyncRoot) 
    { 
     int bytesRead = client.EndReceive(ar); 
     // do some work 

     // Kick off socket to receive async again. 
     client.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, 
       new AsyncCallback(ReceiveCallback), state); 
    } 
} 

// This is commonly called by another thread 
public void SendMessage(string cmdName, Object data) 
{ 
    lock (sendSyncRoot) 
     client.Send(arrayofdata, 0, arraylength, 0); 
} 

它一般一個很好的做法,有專門的syncRoot而不是鎖定在其他類或成員上。這可以避免微妙的死鎖。

+0

絕對會更好地鎖定一些對象,而不是套接字本身。 – 2011-05-30 19:38:14

+0

那麼,從一個線程讀取和從另一個線程寫入時,我從來沒有用任何語言鎖定任何套接字。它在C++,Delphi中工作得很好。除了被管理以外,爲什麼C#不同?爲什麼.NET強制顯式鎖定不需要在非託管代碼中顯式鎖定的操作?如果兩個線程可以寫入,那麼很好,做一些鎖定,但一個寫和一個讀? – 2011-05-30 20:16:50

+0

@馬丁詹姆斯 - 這取決於。如果你想讀寫同時發生,你根本不需要鎖定。但是如果你想要兩個讀數或兩個寫作同時發生,你需要鎖定。我不知道所提供的代碼是僅僅是一個樣本還是真實代碼,但如果真正的代碼是在多個數據中寫入數據,那麼您需要確保沒有其他線程發生干擾。 – 2011-05-30 20:27:04

2

不,不要那樣做。處理套接字的最佳方式是封裝。不要將它暴露給任何人,除了宣佈它的類。通過這樣做,很容易確保一次只有一個接收處於掛起狀態。不需要使用鎖。

至於發送。做這樣的事情:

public class MyClient 
{ 
    private readonly Queue<byte[]> _sendBuffers = new Queue<byte[]>(); 
    private bool _sending; 
    private Socket _socket; 

    public void Send(string cmdName, object data) 
    { 
     lock (_sendBuffers) 
     { 
      _sendBuffers.Enqueue(serializedCommand); 
      if (_sending) 
       return; 

      _sending = true; 
      ThreadPool.QueueUserWorkItem(SendFirstBuffer); 
     } 
    } 

    private void SendFirstBuffer(object state) 
    { 
     while (true) 
     { 
      byte[] buffer; 
      lock (_sendBuffers) 
      { 
       if (_sendBuffers.Count == 0) 
       { 
        _sending = false; 
        return; 
       } 

       buffer = _sendBuffers.Dequeue(); 
      } 

      _socket.Send(buffer); 
     } 
    } 
} 

這種方法不阻止任何呼叫者和所有發送請求輪流處理。