BeginSend需要很长时间才能回调

本文关键字:回调 长时间 BeginSend | 更新日期: 2023-09-27 18:27:17

我使用的是异步方法BeginSend,我需要某种超时机制。我所实现的对于连接和接收超时都很好,但BeginSend回调有问题。即使是25秒的超时也往往不够,甚至会被超过。这对我来说似乎很奇怪,指向了另一个原因。

  public void Send(String data)
    {
        if (client.Connected)
        {
            // Convert the string data to byte data using ASCII encoding.
            byte[] byteData = Encoding.ASCII.GetBytes(data);
            client.NoDelay = true;
            // Begin sending the data to the remote device.
            IAsyncResult res = client.BeginSend(byteData, 0, byteData.Length, 0,
                new AsyncCallback(SendCallback), client);
            if (!res.IsCompleted)
            {
                sendTimer = new System.Threading.Timer(SendTimeoutCallback, null, 10000, Timeout.Infinite);
            }
        }
        else MessageBox.Show("No connection to target! Send");
    }
    private void SendCallback(IAsyncResult ar)
    {
        if (Interlocked.CompareExchange(ref sendTimeoutflag, 1, 0) != 0)
        {
            // the flag was set elsewhere, so return immediately.
            return;
        }
        sendTimeoutflag = 0; //needs to be reset back to 0 for next reception
        // we set the flag to 1, indicating it was completed.
        if (sendTimer != null)
        {
            // stop the timer from firing.
            sendTimer.Dispose();
        }
        try
        {
            // Retrieve the socket from the state object.
            Socket client = (Socket)ar.AsyncState;
            // Complete sending the data to the remote device.
            int bytesSent = client.EndSend(ar);
            ef.updateUI("Sent " + bytesSent.ToString() + " bytes to server." + "'n");
        }
        catch (Exception e)
        {
            MessageBox.Show(e.ToString());
        }
    }
    private void SendTimeoutCallback(object obj)
    {
        if (Interlocked.CompareExchange(ref sendTimeoutflag, 2, 0) != 0)
        {
            // the flag was set elsewhere, so return immediately.
            return;
        }
        // we set the flag to 2, indicating a timeout was hit.
        sendTimer.Dispose();
        client.Close(); // closing the Socket cancels the async operation.
        MessageBox.Show("Connection to the target has been lost! SendTimeoutCallback");
    }

我测试了长达30秒的超时值。30秒的数值被证明是唯一一个永远不会超时的数值。但这似乎有些过头了,我相信还有另一个根本原因。关于为什么会发生这种情况,有什么想法吗?

BeginSend需要很长时间才能回调

不幸的是,没有足够的代码来完全诊断这一问题。您甚至没有显示sendTimeoutflag的声明。这个例子不是自包含的,所以没有办法测试它。你也不清楚到底发生了什么(例如,你只是得到了超时,你完成了发送但仍然得到了超时吗,还有其他事情发生吗?)。

也就是说,我在代码中看到了至少一个严重的错误,那就是您对sendTimeoutflag的使用。SendCallback()方法将此标志设置为1,但它立即将其再次设置回0(这次没有Interlocked.CompareExchange()的保护)。只有在将值设置为0之后,它才会释放计时器。

这意味着,即使您成功地完成了回调,也几乎可以保证超时计时器不知道,并且无论如何都会关闭客户端对象。

您可以通过将分配sendTimeoutflag = 0;移动到实际完成发送操作之后的某个点来解决此特定问题,例如在回调方法的末尾。即便如此,也只有当您采取措施确保计时器回调不能在该点之后执行时(例如,等待计时器的处理完成)。

请注意,即使修复了这个特定的问题,您也可能有其他错误。坦率地说,目前还不清楚你为什么要暂停。也不清楚为什么要使用无锁代码来实现超时逻辑。更传统的锁定(即基于Monitorlock语句)将更容易正确实现,并且可能不会造成明显的性能损失。

我同意这样一个建议,即使用async/await模式而不是显式处理回调方法会更好地为您服务(当然,这意味着使用更高级别的I/O对象,因为Socket不假设异步/await)。