C# 两次处理插座/终接时出现问题?
我正在为一个打开套接字、发出请求并侦听响应的类编写一些代码(我急于添加的不是我的代码,我一点也不相信),这个类以一种我在xunit中测试时无法理解的方式抛出异常。我假设相同的异常发生在“live”中,但是该类被一个单例引用,所以它可能只是隐藏的 问题在xunit中表现为“System.CannotUnloadAppDomainException:卸载appdomain时出错”,内部异常是“System.ObjectDisposedException”,在关闭套接字时(本质上)在终结器内抛出!没有其他对socket的引用,调用close和dispose在socket类上受保护,因此我不清楚该对象还可以如何处理 此外,如果我仅仅捕获并吸收ObjectDisposedException,xunit将在它到达关闭侦听器线程的行时终止 我不知道在要求关闭插座之前如何处理它。 我对套接字的了解只是我发现这个问题后学到的,所以我不知道我是否提供了可能需要的一切。如果不是的话,请滚开C# 两次处理插座/终接时出现问题?,c#,sockets,dispose,finalizer,xunit,C#,Sockets,Dispose,Finalizer,Xunit,我正在为一个打开套接字、发出请求并侦听响应的类编写一些代码(我急于添加的不是我的代码,我一点也不相信),这个类以一种我在xunit中测试时无法理解的方式抛出异常。我假设相同的异常发生在“live”中,但是该类被一个单例引用,所以它可能只是隐藏的 问题在xunit中表现为“System.CannotUnloadAppDomainException:卸载appdomain时出错”,内部异常是“System.ObjectDisposedException”,在关闭套接字时(本质上)在终结器内抛出!没有
public class Foo
{
private Socket sock = null;
private Thread tListenerThread = null
private bool bInitialised;
private Object InitLock = null;
private Object DeInitLock = null;
public Foo()
{
bInitialised = false;
InitLock = new Object();
DeInitLock = new Object();
}
public bool initialise()
{
if (null == InitLock)
return false;
lock (InitLock)
{
if (bInitialised)
return false;
sock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
sock.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, 8);
sock.Bind( /*localIpEndPoint*/);
sock.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(mcIP));
tListenerThread = new Thread(new ThreadStart(listener));
tListenerThread.Start();
bInitialised = true;
return true;
}
}
~Foo()
{
if (bInitialised)
deInitialise();
}
private void deInitialise()
{
if (null == DeInitLock)
return;
lock (DeInitLock)
{
if (bInitialised)
{
sock.Shutdown(SocketShutdown.Both); //throws System.ObjectDisposedException
sock.Close();
tListenerThread.Abort(); //terminates xunit test!
tListenerThread = null;
sock = null;
bInitialised = false;
}
}
}
}
如果此对象符合垃圾收集的条件,并且没有对套接字的其他引用,那么套接字的终结器可能会在对象的终结器之前运行。我想这就是这里发生的事情 在终结器中做这么多工作通常是个坏主意(IMO)。我完全不记得上一次实现终结器是什么时候了——如果实现IDisposable,除非直接引用非托管资源(几乎总是以intptr的形式)。有序关闭应该是标准-终结器通常只在程序正在关闭或者有人忘记处理要启动的实例时运行
(我知道你一开始就澄清了这不是你的代码-我只是想解释一下为什么它有问题。如果你已经知道了一些/所有这些,我向你道歉。)因为垃圾收集器和终结器的工作方式,仅当类是非托管资源(如窗口句柄、GDI对象、全局句柄或任何其他类型的IntPtr)的直接所有者时,才必须使用终结器 终结器不得尝试处置或甚至使用托管资源,否则您将面临调用终结或处置对象的风险。 我强烈建议您阅读本文,了解垃圾收集工作原理的更多细节。此外,这是MSDN参考,请仔细查看底部的建议 简言之:
- 如果对象持有非托管资源,则应实现IDisposable,并且必须实现终结器
- 如果您的对象持有IDisposable对象,它还应该自己实现IDisposable并显式地处置该对象
- 如果对象同时包含非托管和一次性,终结器必须调用两个不同版本的Dispose,一个释放一次性和非托管,另一个只释放非托管。这通常使用由Dipose()和Finalizer()调用的Dispose(bool)函数来完成
- 终结器决不能使用任何其他资源,而只能使用正在释放和自我管理的非托管资源。如果不这样做,将有引用已收集或已处置对象的风险,因为对象在最终确定之前会被临时删除
所以至少我现在有一些弹药可以换了:)谢谢。假设该类在一个单例中被引用,该单例预计将持续整个项目生命周期,那么Finalizer将在关闭时运行(通过IIS或xunit的拆卸)-如果GC将拾取套接字和线程,那么该类是否需要deInit?(即应用实例的生命周期,而不是项目)嗯。。。这里的线索很棘手。坦率地说,显式中止线程无论如何都是一件令人讨厌的事情,但您可能需要一些更干净的方法来消除它。假设xunit卸载AppDomain,它很可能会被中止,但这是值得检查的。我认为它必须给出“CannotUnloadAppDomainException”-我无论如何都会尝试。。这当然不会让同事们感到快乐:)这个建议可以追溯到.NET1.0;我认为更现代的范例是,任何需要终结器的对象都应该关注一种类型的非托管资源(需要终结的实体)的管理,而不是其他。如果一个类需要管理除单一类型的非托管资源以外的任何其他资源,那么每种类型的非托管资源都应该包装在自己的类中(将其转换为托管资源),然后较大的类只应该包含托管资源。