在.NET中,托管资源与非托管资源有什么区别?

3
我正在阅读Wrox's Professional C# 4 and .NET 4中的“内存管理和指针”章节,特别是关于.NET中垃圾回收器如何工作的部分。它说“垃圾回收器不知道如何释放非托管资源(如文件句柄、网络连接和数据库连接)”,这就是为什么这些类应该声明一个析构函数(也称为“终结器”)或实现IDisposable的原因。
似乎所有这些“非托管资源”的例子都与与应用程序无关且独立于.NET Framework的系统交互有关。但是,我不确定是否完全做出了这个区分,所以,
非托管资源具有什么明显的特征,而托管资源没有?

非托管代码为开发人员提供了指针、对硬件的直接控制,并以更快的执行速度而闻名,尽管所有资源管理都必须由开发人员完成。 - Aravind
4个回答

3

你说得对:
托管资源由CLR管理,非托管资源则不是。换句话说:托管资源仅存在于.NET世界中,而非托管资源来自普通的Win32世界。


2
使用托管资源(例如内存),您无需担心在使用后会发生什么;CLR 会处理这些事情。
未经管理的资源(有几种类型:Windows 内核对象、GDI 对象、USER 对象)必须在使用后释放回系统。当您的进程终止时,这会自动发生,但如果在此期间泄漏它们,则会出现大问题,因为您正在泄漏资源,这些资源是在系统中所有进程之间共享的
当然,在 .NET 中有几个类可以包装这些未经管理的资源(使用 dispose/finalize pattern),并为您完成艰苦的工作。如果可以,请使用这些类。

有没有测试可以确定某个东西是否为非托管资源,还是你必须知道所有可能的选项? - smartcaveman
@smartcaveman:不是的,但有一些好的经验法则:如果你看到 IntPtr,那么它就是一个非托管资源;如果你使用 P/Invoke 获取某些东西,那么它就是一个非托管资源;如果你看到使用 unsafe 指针,那么它可能是非托管内存(尽管这很少见)。 - Jon

0

我认为通常情况下,使用.Net框架创建的任何东西都是托管资源。在内部它们可能使用非托管资源,但从你的角度来看它们是托管的。其中一个例外是当您进行P/Invoke时。尽管您创建了一个与.Net一起工作的函数,但调用被传递到.Net“沙盒”之外,因此被视为非托管。

回应@supercat

来自长寿对象的事件可以完全在.net框架内处理,但必须将其视为非托管资源以防止内存泄漏

我认为这里有两个不同的问题。一个是托管与非托管的讨论,另一个是应用程序的内存管理讨论。您可能应该将某些对象视为非托管对象,但这并不意味着它们是非托管的。例如,我认为Brush类是托管的,但您应该通过调用Dispose()来将其视为非托管的。在该类中隐藏/抽象的是非托管对象,您希望Dispose()能够正确处理它们。但是Dispose()实际上并没有释放任何资源,它只是一个模式,您希望开发人员能够正确地实现。

进一步说,大多数非托管对象是通过调用返回指针的CreateXYZ() Win32方法创建的,但需要使用相同指针的`DestroyXYZ()/DeleteXYZ()'方法释放。另一方面,托管对象实现Dispose/Finalize来为您完成此操作。再次强调,您希望托管资源的编写者已经这样做了,但并不保证。

长寿命对象的事件可能完全在 .net 框架内处理,但它们绝对需要被视为非托管资源以防止内存泄漏。 - supercat
非托管资源是垃圾回收器不知道如何清理的东西,如果不清理可能会引起麻烦。垃圾回收器不知道如何清理长寿对象的废弃物订阅事件,这些订阅可能会造成无限制的破坏。"托管资源"是一个具有清理职责但可以在终结器中处理它们的对象。如果这样的对象被遗弃,它通常最终会被清理。如果假定垃圾回收器至少偶尔运行... - supercat
放任未清理的托管资源会导致大量混乱堆积,但在这些对象最终得到处理前可进行的工作量是有限制的。相比之下,即使某些类型的未管理资源的混乱可能最终得到清理,也可能没有任何防止这种混乱在那之前不断积累并拖垮系统的方法。如果一个集合对象将在一周内保持活动状态,并且由一个附加了CollectionChanged事件的枚举器每秒枚举一次,但这些枚举器被放弃而不是Disposed... - supercat
如果枚举器在其Finalize()例程中没有使用某些巧妙的方法来清除事件(请注意,此代码非常棘手!),那么CollectionChanged对象将会积累超过600,000个订阅者 - 这个数字可能会使系统崩溃(请记住,添加每个订阅者都意味着创建一个新的订阅列表 - 当有10,000个订阅者时,该列表将位于大对象堆上)。 - supercat

0
术语“未管理的资源”很令人困惑。更有用的概念是“清理责任”。如果一个对象持有未管理的资源,那意味着三件事:
  1. 它操作一些长期存在的实体,
  2. 该实体可能处于需要清理的状态,以及
  3. 该对象负责提供所需的清理。
通常,“托管资源”一词用于指持有未管理资源的对象,但如果发现这些对象被遗弃,垃圾回收器(通过Finalize例程)将通知它们,并使用此通知来提供清理(以防在调用其正常清理方法之前就被遗弃)。有些人使用“托管资源”一词来指不需要任何清理的东西,但我不喜欢这种用法,因为没有其他好的术语来指代应该手动清理但在正常清理未发生时将使用终结作为后备的东西。
请注意,虽然非托管资源通常是文件的操作系统句柄、GDI 实体等,但认为它们只限于这些方面是错误的。可能存在不访问 .Net 框架之外任何内容的非托管资源;事件处理程序就是一个常见的例子。非托管资源的关键在于需要进行清理,如果未执行此类清理,则会产生一些不良后果。

你对未托管的定义是:“如果你在完成后不清理它,它会产生内存泄漏”。这就是我对你回答的问题。根据你的定义,如果长期存在的对象中有一个对象列表,并且该长期存在的对象没有内部清理程序,则该对象列表也是未托管的资源。 - Daniel Hilgarth
@Daniel Hilgarth:有一个术语来描述所有需要清理但CLR不会执行的实体是很有用的。在什么情况下,有一个术语来指称需要清理的非托管代码实体,但排除那些存在于托管代码世界中但仍需要清理CLR无法执行的实体是有用的? - supercat
我同意术语是有用的,但你使用的术语(“未托管资源”)已经被定义为不同的含义。CLR 无法清理订阅长期存在对象中事件的对象,因为这些对象仍然有引用。我的意思是:你对“未托管资源”的使用与广泛使用的定义不同。 - Daniel Hilgarth
就像我说的那样:这不是“非托管资源”。它只是一个内存泄漏问题。这就是你要找的术语;-) - Daniel Hilgarth
@Daniel Hilgarth:微软最初是通过示例来定义这个术语的;我同意普遍使用已经把这个术语看得比实际更狭窄了。简单来说,微软通过给出一系列共享两个特征的对象的例子来定义这个术语;许多人观察到这些对象都共享一个特征,并认为该术语适用于该特征,尽管另一个共享的特征更为重要。 - supercat
显示剩余7条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接