【问题标题】:SocketAsyncEventArgs and thread safety in .Net.Net 中的 SocketAsyncEventArgs 和线程安全
【发布时间】:2011-05-18 13:54:45
【问题描述】:

我使用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 服务),再次运行相同代码的机会一定非常高。

在不影响使用 SocketAsyncEventArgs 获得的性能的情况下,我需要做什么才能使 ProcessReceive()(和其他相关方法)通常是线程安全的?

以下示例 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);
    }
}

【问题讨论】:

    标签: c# sockets thread-safety


    【解决方案1】:

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

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

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

    【讨论】:

    • 您能否提供一个资源,专门说明 Completed 事件是在 ThreadPool 中的线程上完成的?
    • @harrydev:我找不到,但 SAEA 本质上只是 Begin/End 的一种不同方式,它在线程池线程上调用其回调。
    • 是的,我唯一的问题是本机中的 I/O 完成端口被设计为在同一个线程上完成,线程进入警报状态。这确保了最小的线程上下文切换等,并且不需要线程同步等。我希望它能够以相同的方式工作,无论如何测试可以显示是否是这种情况。
    【解决方案2】:

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

    【讨论】:

    • 虽然从线程安全的角度来看,这确实更容易;尽管不能保证,但它的可扩展性远低于重叠 i/o 版本。这些线程可能不会占用 CPU,但是当您从一个上下文切换到另一个时,它们会占用堆栈空间并消耗 CPU 时间。
    【解决方案3】:

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

    这种模式,除了其他各种优势外,还可以让您处理大量客户......

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-02-28
      • 1970-01-01
      • 2011-08-02
      • 2013-06-10
      • 2010-09-28
      • 2014-08-07
      • 2011-11-06
      相关资源
      最近更新 更多