TcpClient.EndConnect在套接字关闭时抛出NullReferenceException异常。

8
我正在尝试使用 TcpClient.BeginConnect / TcpClient.EndConnect 组合连接到我的服务器。然而,有些事情并不像它们应该的那样工作。
场景如下:
  • 调用 TcpClient.BeginConnect
  • 服务器故意离线(用于测试目的) - 因此无法建立连接。
  • 我关闭应用程序(在过程中调用了 client.Close(),这将关闭套接字,从而停止异步操作
  • TcpClient 连接回调方法发生,给出了 IAsyncResult
  • 使用给定的 IAsyncResult 调用 TcpClient.EndConnect 方法
  • EndConnect 上发生 NullReferenceException
  • 由于最后一个窗体(窗口)已关闭,因此应用程序应退出 - 但它没有,至少在 BeginConnect 操作完成之前不会退出(这很奇怪,因为回调已经被调用)。

exception

这里发生的是捕获了一个NullReferenceException异常。从上面的图片中可以看出,clientar都不是null。问题在于MSDN EndConnect文档没有提到抛出此异常的情况。
基本上,我不知道发生了什么。问题在于我被迫等待应用程序关闭(好像连接操作仍在等待超时)。如果服务器在线,它就可以正常连接和断开连接。
在这种情况下,NullReferenceException是什么意思?如何避免BeginConnect操作在连接无法建立的情况下阻止应用程序关闭?

附加说明在评论中请求):

以下是创建客户端的代码(客户端是一个成员变量):

public void Connect()
{
    try
    {
        lock (connectionAccess)
        {
            if (State.IsConnectable())
            {
                // Create a client
                client = new TcpClient();
                client.LingerState = new LingerOption(false, 0);
                client.NoDelay = true;

                State = CommunicationState.Connecting;

                client.BeginConnect(address, port, onTcpClientConnectionEstablished, null);
            }
            else
            {
                // Ignore connecting request if a connection is in a state that is not connectable 
            }
        }
    }
    catch
    {
        Close(true);
    }
}

还有 Close 方法:

public void Close(bool causedByError)
{
    lock (connectionAccess)
    {
        // Close the stream
        if (clientStream != null)
            clientStream.Close();

        // Close the gateway
        if (client != null)
            client.Close();

        // Empty the mailboxes
        incomingMailbox.Clear();
        outgoingMailbox.Clear();

        State = causedByError ? CommunicationState.CommunicationError : CommunicationState.Disconnected;
    }
}

InnerException提供了关于哪个对象为NULL的更多信息吗? - Tremmors
@Tremmors 不是的。InnerException 为空。StackTrace 也没有提供帮助(onTcpClientConnectionEstablished > EndConnect)。:( - Kornelije Petak
是的,显然是框架的一个bug。如果在调用EndConnect()之前TcpClient被关闭,它应该抛出ObjectDisposedException异常。但我似乎得到了NullReferenceException异常,第一次或者前两次会这样,然后它会开始按预期抛出ObjectDisposedException异常。我建议你简单地像处理ObjectDisposedException异常一样处理它(即“连接超时/失败/其他原因你调用Close()”)。然而,我不知道是否有任何资源泄漏,因为EndConnect()没有完成。可能没有什么GC不能处理的。 :) - Tom
这个问题为什么在.NET 4.8中还没有被解决? - rollsch
4个回答

1

NullReferenceException 可能是由于 TcpClient.Client 为 null 所致。

如果您按照 MSDN 示例TcpClient.BeginConnect 的操作并将 TcpClient 对象作为状态对象传递:

 private void onConnEst(IAsyncResult ar)
 {
      try
      {
           TcpClient client = (TcpClient)ar.AsyncState;
           if(client!=null && client.Client!=null)
           {
                client.EndConnect(ar);
           }
      }
      catch(Exception ex){...}
 }

这应该处理在回调之前调用Close()的情况。

回到你的问题 - 应用程序最终关闭需要多长时间?


为什么要转换ar.AsyncResult?你是不是想说ar.AsyncState?无论如何,我的客户端是一个私有成员,所以我不需要将其传递给异步方法。目前我无法检查TcpClient.Client是否为空(即不在工作中)。然而,根据我的记忆和以往的经验,如果我不调用client.EndConnect,当我尝试关闭应用程序时,它仍然会挂起。这通常持续的时间与连接超时相同(约20秒)。如果我在连接尝试期间不关闭应用程序,则BeginConnect将在相同的时间后失败。 - Kornelije Petak
哎呀,你说得对,ar.AsycResult 应该是 ar.AsyncState。已经修改帖子以反映更改。 - zrc210
你能否发布一下你是如何构建TcpClient以及BeginConnect的呢?在BeginConnect之前,你是否绑定了套接字?另外,虽然这可能无关紧要,但你使用的是哪个.Net Framework? - zrc210
我已经更新了问题以填补缺失的部分。我正在使用.NET 4。 - Kornelije Petak
虽然我没有完全运行你的代码,但是Client.Close()并不需要很长时间。我建议注释掉你代码中的TcpClient部分(仅用于测试目的),看看是否仍然会遇到延迟问题。除此之外,可能有一些奇怪的事情发生在多个线程上锁定connectionAccess上,这可能会导致延迟,如果你在调试代码时没有遇到延迟,那么这是有道理的。 - zrc210
我已经尝试过排除任何TcpClient代码,然后它就可以正常工作。就像我说的,应用程序关闭所需的时间对应于在未建立连接(超时)之后BeginConnect失败所需的时间。因此,我得出结论,是异步BeginConnect线程仍然处于活动状态。关于死锁,这不是问题,因为这些是获取锁的唯一两个位置,并且从您可以看到的内容中,Close(在catch块中)没有包含在此锁中。 - Kornelije Petak

1

显然这是TcpClient类内部的一个bug。我也遇到过这个问题。TcpClient.Dispose可能会将Client字段设置为null,但EndConnect并不希望出现这种情况。


2
欢迎来到SO!您可能需要考虑更详细地阐述错误的细节,以使您的答案对其他可能遇到此问题的人更加全面。 - Levi Botelho

0

我曾经遇到过类似的错误,最终使用了这段代码。我不确定它是否适用于IASyncResult接口,但可能有一种类似的方法来运行此检查。我注意到你的ar.AsyncState == null,所以也许可以从那里开始,即当您正确连接时它是否为空?

private void connConnectCompleted(AsyncCompletedEventArgs e)
{
    if (e.Error != null)
    {
        // Something didn't work...abort captain
        CloseSocket();
        Console.WriteLine(this.GetType().ToString() + @":Error connecting socket:" +  e.Error.Message);
        return;
    }
    // Do stuff with your connection
}

编辑:抱歉我没有意识到我没有发布生成我的AsyncCompletedEventArgs的内容,这与您正在做的事情更相关。您将看到我为什么会想知道ar.AsyncState是否为空。

private void OnConnect(IAsyncResult asyncResult)
{
    if (OnConnectCompleted == null) return; // Check whether something is using this wrapper
    AsyncCompletedEventArgs args;
    try
    {
        Socket outSocket = (Socket) asyncResult.AsyncState;

        // Complete connection
        outSocket.EndConnect(asyncResult);

        args = new AsyncCompletedEventArgs(null);
        OnConnectCompleted(this, args);
    }
    catch (Exception e)
    {
        args = new AsyncCompletedEventArgs(e.Message);
        OnConnectCompleted(this, args);
    }
}

AsyncState用于向回调方法传递一些自定义数据。我没有这样的需求,所以它是null。它不会产生错误。我不理解你的例子(它是不完整的)。IAsyncResult没有错误,我猜你应该提供CloseSocket()方法的代码,因为它是你自己的自定义方法。否则,我不知道你的代码示例如何有帮助。 - Kornelije Petak
CloseSocket() 只是触发一些事件来通知程序的其他部分,然后在套接字上运行 .Close()。 - themartinmcfly

0

这是一个已知的bug

你应该收到“ObjectDisposedException”而不是“NullReferenceException”。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接