2011-05-17 102 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服务将会适用于这个模型。

这种模式,将让你处理大量的客户,除了其他各种优点...