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