C#Socket.BeginSend有时似乎同步?

时间:2010-10-27 10:32:34

标签: c# sockets

尝试通过BeginSend调用为队列发送消息似乎表现为阻塞调用。

特别是我有:

public void Send(MyMessage message)
{
  lock(SEND_LOCK){
    var state = ...
    try {
      log.Info("Begin Sending...");
      socket.BeginSend(message.AsBytes(),0, message.ByteLength, SocketFlags.None, 
         (r) => EndSend(r), state);
      log.Info("Begin Send Complete.");
    }
    catch (SocketException e) {
      ...
    }
  }
}

回调将是这样的:

  private void EndSend(IAsyncResult result) {
     log.Info("EndSend: Ending send.");
     var state = (MySendState) result.AsyncState;
     ...

     state.Socket.EndSend(result, out code);

     log.Info("EndSend: Send ended.");

     WaitUntilNewMessageInQueue();
     SendNextMessage();
  }

大部分时间这都很好,但有时会挂起。记录表明当相同的线程上的BeginSend和EndSend完成时会发生这种情况。 WaitUntilNewMessageInQueue 阻塞,直到队列中有新消息,因此当没有新消息时,它可以等待一段时间退出。

据我所知,这不应该是一个问题,但在某些情况下,BeginSend阻塞导致死锁情况,其中 EndSend WaitUntilNewMessageInQueue 上阻塞(预期),但发送 BeginSend 上阻塞,因为它似乎等待 EndSend 回调返回(不是预期的)。

这种行为不是我所期待的。如果回调没有及时返回,为什么BeginSend有时会阻塞?

3 个答案:

答案 0 :(得分:2)

首先,为什么要锁定Send方法?由于您使用的是BeginSend,因此锁定将在发送完成之前释放。结果是多个发送可以同时执行。

其次,不要写(r) => EndSend(r),只需写EndSend(不带任何参数)。

Thrid:您不需要在您的州内包含套接字。您的EndSend方法与其他任何实例方法一样。因此,您可以直接访问socket字段。

至于你的僵局,很难说。您委托可能与它有关(由编译器/运行程序进行优化)。但我对那个领域一无所知。

需要更多帮助?发布更多代码。但我建议你解决上面的问题(所有四个问题),然后再试一次。

答案 1 :(得分:1)

您在运行哪种操作系统?

你确定你看到了你认为你所看到的东西吗?

MSDN page上的注释表示如果没有OS缓冲区空间来启动异步发送,Send() CAN会阻塞,除非您已将套接字置于非阻塞模式。可能是这样吗?您是否可能非常快速地发送数据并将TCP窗口填充到对等方?如果你闯入调试器,调用堆栈会显示什么?

其余的是基于我对所涉及的基础本地技术的理解而进行的猜测......

如果线程退出,Send()的注释可能是错误的I / O被取消,这几乎肯定取决于底层操作系统,因为它是一个低级IO完成端口/重叠的I / O问题,随之改变Windows Vista(请参阅此处:http://www.lenholgate.com/blog/2008/02/major-vista-overlapped-io-change.html)并且鉴于他们错了,那么他们可能会错误地了解如何在以后的操作系统上调度完成(对EndSend()的调用)。从Vista开始,如果.Net套接字包装器在套接字上启用了正确的选项,则可能会在发布线程上调度完成(请参阅here我在哪里谈论FILE_SKIP_COMPLETION_PORT_ON_SUCCESS)...但是如果是这种情况,那么很可能你会看到这种行为很多,因为最初大多数发送都可能完成“排队”,所以你会看到大多数完成发生在同一个线程上 - 我很确定情况并非如此,.Net不会在不询问...

的情况下启用此选项

答案 2 :(得分:0)

这是检查它是否同步完成的方式,以避免在另一个线程上回调。

单次发送:

var result = socket.BeginSend(...);
if (result.CompletedSynchronously)
{
    socket.EndSend(result);
}

对于多个发送的队列,您可以循环并完成所有同步发送:

while (true)
{
    var result = socket.BeginSend(...);
    if (!result.CompletedSynchronously)
    {
        break;
    }
    socket.EndSend(result);
}