2013-10-22 34 views
2

我们有一个服务器应用程序,通过TCP套接字与客户端进行通信。运行几周后,它会崩溃,并导致无法处理的NullReferenceException。我已经能够用一个非常小的控制台程序重现异常,但似乎在内部套接字线程池中存在未处理的异常。所以我不能用任何try/catch块来处理它,因为它不在我的控制之下。NullReferenceException,C#套接字BeginConnect中的错误?

有没有人对此有任何意见?它是一个框架错误,或者我怎样才能捕捉套接字线程池中的异常(所以我们的应用程序没有崩溃)? 以下是经过几次迭代(3-10)后生成异常的示例代码。了解服务器处于脱机状态很重要,因此套接字无法连接。它用于Visual Studio 2010和.Net框架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) 
+0

我面临同样的问题。我非常有信心这是一个框架中的错误。 ConnectCallback函数在这里http://referencesource.microsoft.com/#System/net/System/Net/Sockets/Socket.cs,7be8fddc24c74b66,references没有检查'asyncResult'不为空,这可能是一些竞争条件。既然你有再生案例,你应该提交连接。 http://connect.microsoft.com/ –

+0

可能重复[什么是NullReferenceException,我该如何解决它?](http://stackoverflow.com/questions/4660142/what-is-a-nullreferenceexception-and-我怎么办 - 修复它) – Rob

+0

@rob - 当然不是。请仔细阅读,这发生在.NET自己的代码中(尝试代码)。 –

回答

1

您提供的示例代码重复调用BeginConnect无需等待异步操作完成。

粗略地说,你这样做

while(true) 
{ 
    socket.BeginConnect(...); 
    Sleep(1000); 
} 

所以,当你的线程启动时,它首先调用BeginConnect(),然后等待1秒钟,然后再次调用BeginConnect()而以前的通话仍在执行。

在我的电脑上,它给了我一个InvalidOperationException,但我猜这个异常类型可能取决于CLR版本(我使用的是.NET 4.5.1)。

这里有3个不同的解决方案:

  1. 取消与Socket.EndConnect()
  2. 等待异步操作的异步操作与IAsyncResult.AsyncWaitHandle.WaitOne()
  3. 完成不要使用BeginConnect()和使用Connect()代替
+0

对我来说更重要的是要理解try/catch块中没有捕捉到异常的原因(在上面的代码中应该抓住和跟踪所有异常),但是它立即发生应用程序域未处理的异常(导致应用程序完全崩溃)? –

+0

那么,当我测试异常是在'[BeginConnect]错误{0}'捕获。无论如何,你的代码中存在严重错误。在寻找.NET框架中的错误之前,您应该尝试修复它。 –

1

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

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

请改变你的回调方法的名称MyConnectCallback,而BeginConnect电话更改为:

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

看看是否改变任何东西。

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

+0

降价的任何理由? –

+0

没有downvoted,但这是不相关的名称,我改变了它,问题依然存在。 –

1

我非常有信心这个无法解决的错误是由Socket代码中的错误引起的,您应该将其报告给connect

下面是从Socket.cs代码在.NET参考源的提取物:http://referencesource.microsoft.com/#System/net/System/Net/Sockets/Socket.cs,938ed6a18154d0fc

private void ConnectCallback() 
{ 
    LazyAsyncResult asyncResult = (LazyAsyncResult) m_AcceptQueueOrConnectResult; 

    // If we came here due to a ---- between BeginConnect and Dispose 
    if (asyncResult.InternalPeekCompleted) 
    { 
    // etc. 
     return; 
    } 
} 

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

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事件来确定它是否必须传递给套接字成员方法。

问题是我想这个事件有时不是空的,而m_AcceptQueueOrConnectResult可能是空的,这会导致问题,在一个不可捕获的线程中。

这就是说,问题的根本原因在于您的代码在其他人注意到时首先出现问题。为了避免这种可怕的无法解决的错误,只要确保在错误发生时在套接字上调用CloseDispose,并且这会在内部清除m_RegisteredWait成员。例如,BeginConnect文档中提到:

要取消对BeginConnect方法的挂起调用,请关闭套接字。 当异步操作处于 进度中时调用Close方法时,将调用提供给BeginConnect方法的回调。 对EndConnect方法的后续调用将抛出 ObjectDisposedException来指示操作已取消 。

在你的榜样,只需添加下面一行到你的回调代码:

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

现在,你仍然有错误,但他们将是正常的错误。