为什么我得到这个结果(同一个线程在退出前输入两次)?

时间:2014-11-19 15:31:34

标签: c# multithreading sockets

我继承了我正在尝试调试的套接字回调例程。它与 EndReceive()进行异步TCP通信,紧接着是 BeginReceve(),因此它始终在监听。它充满了“点击时”断点,显示一条消息并继续进行诊断。

因为它是一个异步回调,系统可能会多次为一个 BeginReceive()调用它,即使它正在处理前一个回调。通常这是一个新的线程,这就是为什么我有一个锁(一个关键部分)为整个事情。但有时它似乎甚至在它退出之前就被同一个线程重新进入。这怎么可能?我做错了什么?

他是我的踪迹(使用'When Hit'断点)。 。 。

Socket_DataArrival() – Entering … managedThreadID=11
Socket_DataArrival() - first statement in lock . . . managedThreadID=11
Socket_DataArrival() - EndReceive() ... managedThreadID=11
Socket_DataArrival() - BeginReceive() . . . managedThreadID=11
Socket_DataArrival() – Entering … managedThreadID=11
Socket_DataArrival() - first statement in lock . . . managedThreadID=11
Socket_DataArrival() - EndReceive() ... managedThreadID=11
Socket_DataArrival() - BeginReceive() . . . managedThreadID=11
Socket_DataArrival() - exiting at end of routine . . . managedThreadID=11
Socket_DataArrival() - exiting at end of routine . . . managedThreadID=11

这是例程(专有代码注释掉)。 。 。

   private void Socket_DataArrival(IAsyncResult ar)
   {
       StateObject stateObject;
       int bytesReceived;
       int managedThreadId = Thread.CurrentThread.ManagedThreadId;

       lock (inputLock)
       {                 // "Entering..."
           try
           {
               _Receiving = false;  //"first statement in lock"
               stateObject = (StateObject)ar.AsyncState;
               bytesReceived = stateObject.sSocket.EndReceive(ar);    
               _Receiving = true;

               _StateObject = new StateObject(2048, _TCPConn);  //2048 = arbitrary number
               _StateObject.tag = "Socket_DataArrival ";

               _TCPConn.BeginReceive(
                    _StateObject.sBuffer,
                    0,
                    _StateObject.sBuffer.Length,
                    SocketFlags.None,
                    new AsyncCallback(Socket_DataArrival),
                    _StateObject);

            }
            catch (Exception exc)
            {
                subs.LogException("Socket_DataArrival", exc);
            }

            // proprietary stuff goes here

        }  // end critical section
        return;
    }

inputLock在类级别定义为。 。 。

private Object inputLock = new Object();

同一个帖子如何在第一次退出之前第二次进入它?

3 个答案:

答案 0 :(得分:3)

当您调用BeginReceive()并且数据已经可用时,您会立即收到回调,并在调用BeginReceive()的线程上进行回调。

这听起来似乎是合理的,因为基础I / O模型几乎可以保证基于I / O完成端口,并且在Windows XP之后的操作系统上(所有当前支持的操作系统),您可以告诉IOCP'跳过完成端口处理“成功时”,而是立即将异步数据返回给调用者。

所以,我假设发生的是BeginReceive()调用WSARecv()并立即完成,因为数据可用,因此调用代码会立即在调用线程上执行回调。如果没有可用的数据,则WSARecv()将返回IO_PENDING并且I / O最终将完成(当数据到达时),并且与套接字的IOCP关联的其中一个线程将处理完成并调用处理程序。

某些数据流模式比其他模式更容易导致这种情况发生,当然,它依赖于网络以及数据的流动方式。使用异步发送也更有可能发生......

解决此问题的最佳方法是让您的I / O处理程序简单地将所有完成放入一个只有一个线程可以处理且无法递归处理的队列中。这样就会给你一个设计,从而不会发生这个问题。我为ACCU出版物Overload写了这样一个设计here

答案 1 :(得分:2)

lock仅阻止不同的主题进入关键部分,而不是同一部分。

由于您使用相同的回调(也是_TCPConn.BeginReceive)在Socket_DataArrival内拨打Socket_DataArrival,因此只需在从_TCPConn接收数据时再次输入此回调。

尝试对_TCPConn使用不同的回调,或尝试在BeginReceive()之外拨打Socket_DataArrival()

答案 2 :(得分:1)

当你调用BeginReceive()时,你允许下一个回调发生(在那个线程中)。锁定释放后放置BeginReceive。