.Net中的SocketAsyncEventArgs和线程安全

时间:2011-05-17 20:47:52

标签: c# sockets thread-safety

我使用MSDN和(大多数)CodeProject中的示例来编写套接字服务器。我试图了解代码的线程安全性。所有套接字事件都会触发IO_Completed方法,该方法检查SAEA的最后操作类型(发送或接收):

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);
    }
}

3 个答案:

答案 0 :(得分:1)

只需同步需要同步的内容。 IO_Completed方法本身是线程安全无关的,不需要更改。

假设您的DataHoldingUserToken(以及其他变量,例如prefixHandler)不是线程安全的,那么它们将需要受到保护。据我所知,一个简单的lock应该这样做。

心理模型是:IO_Completed可以随时使用不同的参数调用;每个都在ThreadPool线程上运行。

答案 1 :(得分:0)

我最近实施了类似的东西。它通过tcp连接处理消息。我创建了一个负责接受传入连接的线程。然后该线程将生成一个新线程来处理每个连接。这些线程在等待来自网络的I / O时被阻塞,因此它们不会占用CPU资源。如果您的连接不共享任何内容,则不需要线程安全。

答案 2 :(得分:0)

我建议使用异步编程模型,基本上客户端将调用BeginProcessReceive并传入回调,并在回调中执行EndProcessReceive。您可以使用任一任务,或者在4.0之前调用ThreadPool.QueueUserWorkItem。我猜这里,但看起来StartReceived或StartSend是阻塞方法,可以执行到他们自己的(线程池)线程。正如您所提到的,调用WCF服务将适合此模型。

除了各种其他优点之外,这个模型还可以让你处理大量的客户......