2011-05-17 45 views
1

我已經使用MSDN和(大部分)CodeProject中的示例編寫套接字服務器。我試圖讓我的頭腦圍繞代碼的線程安全性。所有的套接字事件觸發IO_Completed方法來檢查該SAEA上一操作類型(發送或接收):SocketAsyncEventArgs和.Net中的線程安全

void IO_Completed(object sender, SocketAsyncEventArgs e) 
{ 
    // determine which type of operation just completed and call the associated handler 
    switch (e.LastOperation) 
    { 
     case SocketAsyncOperation.Receive: 
      ProcessReceive(e); 
      break; 
     case SocketAsyncOperation.Send: 
      ProcessSend(e); 
      break; 
     default: 
      throw new ArgumentException("The last operation completed on the socket was not a receive or send"); 
    }  
} 

思考來電,確實ProcessReceive()需要完全線程安全的,因爲它可以被稱爲多如果有很多客戶端,或者在某種程度上阻塞了它,以便在下一個事件再次調用它之前完全完成?我所做的不僅僅是將收到的消息直接反彈回客戶端(這正是示例所做的)。

即使在這些例子中,ProcessReceive()也是一個相當長的方法(見下文),並且肯定必須處於第二個線程的破壞風險之中。當我添加代碼時,我需要做些明智的事情(調用WCF服務),再次運行相同代碼的機會必須非常高。

我需要做什麼才能使ProcessReceive()(和其他相關方法)通常是線程安全的,而不會影響使用SocketAsyncEventArgs獲得的性能?

實施例ProcessReceive()以下方法:

private void ProcessReceive(SocketAsyncEventArgs receiveSendEventArgs) 
{ 
    DataHoldingUserToken receiveSendToken = 
       (DataHoldingUserToken)receiveSendEventArgs.UserToken; 

    if (receiveSendEventArgs.SocketError != SocketError.Success) 
    { 
     receiveSendToken.Reset(); 
     CloseClientSocket(receiveSendEventArgs); 
     return; 
    } 

    if (receiveSendEventArgs.BytesTransferred == 0) 
    { 
     receiveSendToken.Reset(); 
     CloseClientSocket(receiveSendEventArgs); 
     return; 
    } 

    Int32 remainingBytesToProcess = receiveSendEventArgs.BytesTransferred; 

    if (receiveSendToken.receivedPrefixBytesDoneCount < 
         this.socketListenerSettings.ReceivePrefixLength) 
    { 
     remainingBytesToProcess = prefixHandler.HandlePrefix(receiveSendEventArgs, 
        receiveSendToken, remainingBytesToProcess); 

     if (remainingBytesToProcess == 0) 
     { 
      StartReceive(receiveSendEventArgs); 
      return; 
     } 
    } 

    bool incomingTcpMessageIsReady = messageHandler 
       .HandleMessage(receiveSendEventArgs, 
       receiveSendToken, remainingBytesToProcess); 

    if (incomingTcpMessageIsReady == true) 
    { 
     receiveSendToken.theMediator.HandleData(receiveSendToken.theDataHolder); 
     receiveSendToken.CreateNewDataHolder(); 
     receiveSendToken.Reset(); 
     receiveSendToken.theMediator.PrepareOutgoingData(); 
     StartSend(receiveSendToken.theMediator.GiveBack()); 
    } 
    else 
    { 
     receiveSendToken.receiveMessageOffset = receiveSendToken.bufferOffsetReceive; 
     receiveSendToken.recPrefixBytesDoneThisOp = 0; 
     StartReceive(receiveSendEventArgs); 
    } 
} 

回答

1

只是同步需要同步的內容。方法本身是線程安全不可知的,不需要改變。

假設您的DataHoldingUserToken(以及其他變量,例如prefixHandler)不是線程安全的,則需要對它們進行保護。據我所知,一個簡單的lock應該做的。

心智模式是這樣的:IO_Completed可以隨時用不同的參數調用;它們中的每一個都在ThreadPool線程上運行。

+0

謝謝,這有助於清除霧! – Alan 2011-05-18 11:36:55

+0

你能否提供一個資源,專門說明Completed事件是在ThreadPool的線程上完成的? – nietras 2013-07-02 20:01:01

+0

@harrydev:我找不到一個,但SAEA基本上是做'Begin' /'End'的一種不同方式,它在一個線程池線程上調用它的回調函數。 – 2013-07-02 20:21:40

0

最近實施的這樣的事情。它通過tcp連接處理消息。我創建了一個負責接受傳入連接的線程。那個線程會產生一個新線程來處理每個連接。這些線程在等待來自網絡的I/O時被阻塞,因此他們沒有吃掉CPU資源。如果您的連接不共享任何內容,則不需要線程安全。

+0

雖然從線程安全的角度來看,這確實更容易;儘管這不能保證,但它的重要性遠低於重疊的I/O版本。這些線程可能不會佔用CPU,但是它們爲堆棧使用空間,並且在從上下文切換到另一個時耗費CPU時間。 – 2011-05-18 07:25:12

0

我建議使用異步編程模型,基本上客戶端會調用BeginProcessReceive並傳遞迴調,並在回調中執行EndProcessReceive。您可以使用任一任務,或者如果4.0之前調用ThreadPool.QueueUserWorkItem。我在這裏猜測,但它看起來像StartReceived或StartSend阻塞方法,可以執行到自己的(線程池)線程。正如你所提到的那樣調用WCF服務將會適用於這個模型。

這種模式,將讓你處理大量的客戶,除了其他各種優點...