我们有一个服务器应用程序,通过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)
答案 0 :(得分:1)
您提供的示例代码重复调用BeginConnect
,而不等待异步操作完成。
粗略地说,你正在那样做
while(true)
{
socket.BeginConnect(...);
Sleep(1000);
}
因此,当您的线程启动时,首先调用BeginConnect()
,然后等待一秒钟,然后再次呼叫BeginConnect()
,同时前一个呼叫仍在执行。
在我的计算机上,它给了我InvalidOperationException
,但我想异常类型可能取决于CLR版本(我使用的是.NET 4.5.1)。
以下是3种不同的解决方案:
Socket.EndConnect()
IAsyncResult.AsyncWaitHandle.WaitOne()
BeginConnect()
并使用Connect()
代替答案 1 :(得分:1)
我非常有信心这个无法捕获的错误是由套接字代码中的错误引起的,您应该将其报告给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
事件来确定它是否必须传递给套接字成员方法。
问题是我想这个事件有时不是null,而m_AcceptQueueOrConnectResult
可能是null,导致问题,在一个无法捕获的线程中。
话虽如此,问题的根本原因在于您的代码首先出现问题,正如其他人所指出的那样。为了避免这种可怕的无法捕获的错误,只需确保在发生错误时在套接字上调用Close
或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();
}
}
现在,您仍然会遇到错误,但这些都是正常错误。
答案 2 :(得分:0)
如果仔细查看堆栈跟踪,您会在NullReferenceException
中看到System.Net.Sockets.Socket.ConnectCallback
。如果查看代码,您会看到有一个名为ConnectCallback
的方法。
这就是我们所说的“巧合”。
请将回调方法的名称更改为MyConnectCallback
,并将BeginConnect
来电更改为:
var ar = socket.BeginConnect(host, 6500, new AsyncCallback(MyConnectCallback), socket);
看看是否有任何改变。
如果我是正确的,并且您的ConnectCallback
方法从未被调用过,那么我也不得不想知道您的代码是如何工作的。