2013-10-22 33 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(); 
     } 
    } 

現在,你仍然有錯誤,但他們將是正常的錯誤。