NullReferenceException,C#套接字中的bug开始连接?

NullReferenceException,C#套接字中的bug开始连接?,c#,.net,sockets,nullreferenceexception,C#,.net,Sockets,Nullreferenceexception,我们有一个通过TCP套接字与客户机通信的服务器应用程序。在运行数周后,它会崩溃,出现无法处理的NullReferenceException。我已经能够用一个非常小的控制台程序重现异常,但在内部套接字线程池中似乎存在未处理的异常。因此,我无法处理任何try/catch块,因为它不在我的控制范围内 有人知道这件事吗?这是一个框架错误还是我如何捕捉套接字线程池上的异常(这样我们的应用程序就不会崩溃)? 下面是经过几次迭代(3-10)后生成异常的示例代码。重要的是要知道服务器处于脱机状态,因此套接字无法

我们有一个通过TCP套接字与客户机通信的服务器应用程序。在运行数周后,它会崩溃,出现无法处理的NullReferenceException。我已经能够用一个非常小的控制台程序重现异常,但在内部套接字线程池中似乎存在未处理的异常。因此,我无法处理任何try/catch块,因为它不在我的控制范围内

有人知道这件事吗?这是一个框架错误还是我如何捕捉套接字线程池上的异常(这样我们的应用程序就不会崩溃)? 下面是经过几次迭代(3-10)后生成异常的示例代码。重要的是要知道服务器处于脱机状态,因此套接字无法连接。它用于Visual studio 2010和.Net framework 4.0

internal class Program
{
    private static string host;

    private static Socket socket;

    private static void Main(string[] args)
    {
        Trace.Listeners.Add(new ConsoleTraceListener());

        AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);

        socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

        host = "127.0.0.1";
        //aslo the problem is happening whe the host is other network ip address
        //host = "192.168.0.1";

        //when in other thread doesn not crash application
        //Task.Factory.StartNew(() => StartConnecting());

        //also crashing the application
        //Task.Factory.StartNew(() => StartConnecting(), TaskCreationOptions.LongRunning);

        //when it is regular thread the exception occurs
        ///*
        var thread = new Thread(new ThreadStart(StartConnecting));
        thread.Start();
        //*/

        //when it is blocking exception also occurs
        //StartConnecting();
        Console.WriteLine("Press any key to exit ...");
        Console.ReadKey();
    }

    private static void StartConnecting()
    {
        try
        {
            int count = 0;
            while (true)
            {
                try
                {
                    // if i must switch to Socket.Connect(...)?
                    Trace.WriteLine(string.Format("Connect Try {0} begin", ++count));

                    var ar = socket.BeginConnect(host, 6500, new AsyncCallback(ConnectCallback), socket);

                    Trace.WriteLine(string.Format("Connect Try {0} end", count));
                }
                catch (Exception err)
                {
                    Trace.WriteLine(string.Format("[BeginConnect] error {0}", err.ToString()));
                }
                System.Threading.Thread.Sleep(1000);
                //will see the exception more quick
            }
        }
        catch (Exception e)
        {
            Trace.WriteLine(string.Format("[StartConnecting] error {0}", e.ToString()));
        }
    }

    private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
    {
        string msg = e.ExceptionObject.ToString();

        Trace.WriteLine(string.Format("[CurrentDomain_UnhandledException] isTerminating={0} error {1}", e.IsTerminating, msg));

        Trace.WriteLine("Exiting process");

        //the other processing threads continue working
        //without problems untill there is thread.sleep
        //Thread.Sleep(10000);
    }

    private static void ConnectCallback(IAsyncResult ar)
    {
        try
        {
            Trace.WriteLine("[ConnectCallback] enter");
            var socket = (Socket)ar.AsyncState;
            socket.EndConnect(ar);

            Trace.WriteLine("[ConnectCallback] exit");
        }
        catch (Exception e)
        {
            Trace.WriteLine(string.Format("[ConnectCallback] error {0}", e.ToString()));
        }
    }
}
应用程序启动后,将不可避免地发生崩溃:

[CurrentDomain_UnhandledException] isTerminating=True error System.NullReferenceException: Object reference not set to an instance of an object.
   at System.Net.Sockets.Socket.ConnectCallback()
   at System.Net.Sockets.Socket.RegisteredWaitCallback(Object state, Boolean timedOut)
   at System.Threading._ThreadPoolWaitOrTimerCallback.PerformWaitOrTimerCallback(Object state, Boolean timedOut)

您提供的示例代码在不等待异步操作完成的情况下反复调用
BeginConnect

粗略地说,你正在这么做

while(true)
{
    socket.BeginConnect(...);
    Sleep(1000);
}
因此,当线程启动时,它首先调用
BeginConnect()
,然后等待一秒钟,然后在上一个调用仍在执行时再次调用
BeginConnect()

在我的计算机上,它给了我一个
invalidoOperationException
,但我想异常类型可能取决于CLR版本(我使用的是.NET 4.5.1)

以下是3种不同的解决方案:

  • 使用
    Socket.EndConnect()
  • 等待异步操作使用
    IAsyncResult.AsyncWaitHandle.WaitOne()完成。
  • 不要使用
    BeginConnect()
    ,而是使用
    Connect()

  • 如果仔细查看堆栈跟踪,您将看到
    NullReferenceException
    发生在
    System.Net.Sockets.Socket.ConnectCallback
    中。如果查看代码,您将看到有一个名为
    ConnectCallback
    的方法

    这就是我们所说的“巧合”

    请将回调方法的名称更改为
    MyConnectCallback
    ,并将
    BeginConnect
    调用更改为:

    var ar = socket.BeginConnect(host, 6500, new AsyncCallback(MyConnectCallback), socket);
    
    看看这会不会改变什么


    如果我是正确的,并且您的
    ConnectCallback
    方法从未被调用,那么我也不得不想知道您的代码到底是如何工作的。

    我非常确信这个不可修补的错误是由套接字代码中的错误引起的,您应该将其报告给

    以下是从.NET参考源的Socket.cs代码中摘录的内容:

    此回调由另一个静态方法调用:

    private static void RegisteredWaitCallback(object state, bool timedOut)
    {
      Socket me = (Socket)state;
    
      // Interlocked to avoid a race condition with DoBeginConnect
      if (Interlocked.Exchange(ref me.m_RegisteredWait, null) != null)
      {
        switch (me.m_BlockEventBits)
        {
        case AsyncEventBits.FdConnect:
          me.ConnectCallback();
          break;
    
        case AsyncEventBits.FdAccept:
          me.AcceptCallback(null);
          break;
        }
      }
    }
    
    这个静态方法永远不会被取消注册,它总是被调用,但它依赖于
    m_RegisteredWait
    事件来确定它是否必须传递给套接字成员方法

    问题是,我假设此事件有时不为null,而
    m_AcceptQueueOrConnectResult
    可以为null,这会导致不可修补线程中出现问题

    也就是说,问题的根本原因是,正如其他人所注意到的,您的代码首先表现出问题。为了避免这个可怕的不可修补错误,只要确保在发生错误时在套接字上调用
    Close
    Dispose
    ,这将在内部清除
    m_RegisteredWait
    成员。例如,BeginConnect文档说明:

    要取消对BeginConnect方法的挂起调用,请关闭套接字。 在异步操作处于运行状态时调用Close方法时 进行时,将调用提供给BeginConnect方法的回调。 对EndConnect方法的后续调用将引发 ObjectDisposedException,指示已执行该操作 取消了

    在您的示例中,只需在回调代码中添加以下行:

     private static void ConnectCallback(IAsyncResult ar)
        {
            try
            {
             ...
            }
            catch (Exception e)
            {
              if (_socket != null) _socket.Dispose();
            }
        }
    

    现在,您仍然会有错误,但它们将是正常错误。

    对我来说更重要的是理解为什么在try/catch块中没有捕获异常(在代码中,首先应该捕获并跟踪所有异常),但它会立即进入应用程序域未经处理的异常(导致应用程序完全崩溃),当我测试时,异常被捕获在
    [BeginConnect]错误{0}
    中。无论如何,您的代码中有严重错误。在.NET framework中查找bug之前,您应该尝试修复它。我也面临同样的问题。我很有信心这是框架中的一个bug。此处的ConnectCallback函数未检查“asyncResult”是否为null,这可能是某种竞争条件。由于您有一个复制案例,您应该提交以连接。@rob的可能副本-当然不是。请仔细阅读,这发生在.NET自己的代码中(请尝试代码)。我已在此处向Microsoft发出错误请求:未否决,但这与名称无关,我更改了它,问题仍然存在。
     private static void ConnectCallback(IAsyncResult ar)
        {
            try
            {
             ...
            }
            catch (Exception e)
            {
              if (_socket != null) _socket.Dispose();
            }
        }