3

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)
4

3 に答える 3

1

このキャッチできないエラーは Socket コードのバグが原因であると確信しており、connectに報告する必要があります。

以下は、.NET リファレンス ソースの Socket.cs コードからの抜粋です: 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問題は、キャッチできないスレッドで問題を引き起こす null になる可能性がある一方で、このイベントが null でない場合があることです。

そうは言っても、問題の根本的な原因は、他の人が指摘したように、最初にコードに問題があるという事実です。この恐ろしいキャッチできないエラーを回避するには、エラーが発生したときにソケットでCloseorを呼び出すようにしてください。これにより、メンバーDisposeが内部的にクリアされます。m_RegisteredWaitたとえば、BeginConnect のドキュメントには次のように書かれています。

BeginConnect メソッドへの保留中の呼び出しをキャンセルするには、Socket を閉じます。非同期操作の進行中に Close メソッドが呼び出されると、BeginConnect メソッドに提供されたコールバックが呼び出されます。EndConnect メソッドへの後続の呼び出しは、ObjectDisposedException をスローして、操作がキャンセルされたことを示します。

あなたの例では、次の行をコールバック コードに追加するだけです。

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

これで、まだエラーが発生しますが、通常のエラーになります。

于 2016-01-31T17:08:35.937 に答える
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()代わりに使用してください
于 2013-10-22T18:38:39.233 に答える
0

スタック トレースを注意深く見るとNullReferenceExceptionSystem.Net.Sockets.Socket.ConnectCallback. コードを見ると、 という名前のメソッドがあることがわかりますConnectCallback

それを私たちは「偶然」と呼んでいます。

コールバック メソッドの名前を にMyConnectCallback変更し、BeginConnect呼び出しを次のように変更してください。

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

それが何かを変えるかどうか見てください。

私が正しく、ConnectCallbackメソッドが呼び出されない場合、コードがどのように機能するのか疑問に思うこともできます。

于 2013-10-22T19:44:39.760 に答える