在处理时,托管资源和本机资源有什么区别?(.NET)

28

我正在阅读有关如何实现IDisposable的MSDN文章,但是我对文章中提到的托管资源和本机资源之间的区别感到不确定。

我有一个类,在被处理时必须处理其中的两个字段。我应该将它们视为托管资源(仅在disposing = true时进行处理)还是本机资源?

3个回答

24

补充Brian的回答和您的评论/问题:

托管/非托管资源之间的区别在于垃圾回收器(Garbage Collector)知道托管资源但不知道非托管资源。我知道这个回答不是很具体,但是这个区别非常重要。

为了帮助划清界限,以下是GC运行和清理内存的简短版本(可能有些错误):

垃圾收集器知道所有托管对象,但当垃圾回收运行时,它起初不知道任何给定的对象是否仍在使用或是否可以释放。它通过将所有对象标记为垃圾来确定是否可以清理对象,然后从应用程序根到所有引用的对象进行遍历。每个具有与根(直接或间接)的关系的对象都被标记为可达,并且不再被视为垃圾。在GC遍历每个可达对象后,它会清理其余未使用的对象。

在几乎所有的情况下,使用.NET框架对象时,您可以确信对象是托管的(.NET提供了几乎所有非托管资源的托管封装,以确保它们被正确清理);其他第三方组件也可能连接到Win32 API(或者您的组件这样做),这些对象可能会引起关注。

有一些.NET对象可以被认为是非常不可管理的,Graphics库的组件就是一个例子。

大多数“.NET内存泄漏”实际上并不是真正的内存泄漏。通常发生在您认为已经从使用中删除了一个对象,但实际上该对象仍然与应用程序有某种引用。常见的例子是添加事件处理程序( obj.SomeEvent += OnSomeEvent 或者AddHandler obj.SomeEvent, AddressOf OnSomeEvent )但没有将它们删除。

这些“残留引用”在技术上不算是内存泄漏,因为您的应用程序仍然在使用它们;但是如果有足够多的这些引用,您的应用程序可能会受到严重的性能影响,并可能显示出资源问题(OutOfMemoryExceptions、无法获取窗口句柄等)。

我是一名中级的.NET开发者,不幸地亲自遇到过这些问题。我建议使用ANTS Profiler来帮助熟悉残留引用(有一个免费试用版),或者如果你想进行更深入的研究,可以使用WinDbg和SOS.DLL来查看托管堆。如果你决定研究后者,我建议阅读Tess Ferrandez的博客;她有很多关于有效使用Windbg的教程和建议。


4
我喜欢你的回答。托管资源和非托管资源最重要的区别不在于它们是否存在于GC世界中,而是是否能够在被遗弃时由GC知道如何进行必要的清理。如果未能清理某些内容可能会影响系统功能,但是二级GC可以处理它,那么它就是托管资源。如果未能清理某些内容可能会影响系统功能,即使反复进行二级GC也无法解决,那么它就是非托管资源。 - supercat

20

被管理的资源是另一种实现了IDisposable接口的托管类型。您需要在使用任何其他IDisposable类型时调用Dispose()方法。本地资源是托管世界之外的任何东西,例如本地Windows句柄等。


编辑: 回答评论中的问题(过长无法作为评论)

不是,那只是一种托管类型。正确构造的类型,没有实现IDisposable接口将由垃圾回收器处理,您不必做其他任何事情。如果您的类型直接使用本地资源(例如通过调用Win32库),则必须在类型上实现IDisposable接口,并在Dispose方法中释放资源。如果您的类型使用由另一个实现IDisposable接口的类型封装的本机资源,则必须在您的类型的Dispose方法中调用此类型的实例上的Dispose()方法。


那么,如果一个托管类型没有实现IDisposable接口,它是一种非托管资源吗?也许我被类型和资源搞混了... - Larry Fix
@Yooder:如何拥有一个由类型未表示的托管资源? - Brian Rasmussen
@LarryFix:不同的人对术语有不同的理解,但我认为,一个对象如果没有要求“其他东西”代表它做任何事情,除非另有通知,并且对他人造成损害,那么它就不持有“任何”资源。 - supercat

1

简短的回答是,任何你绕过CLR(到操作系统)获取的内容都可以称为“本地”。

  • 非托管内存分配。如果在托管类CantStayManaged中使用“new”创建了一块内存块,则CantStayManaged负责释放此内存(资源)。
  • 文件、管道、事件、同步构造等的句柄-作为一个规则,如果调用WinAPI来获取指向资源的指针/句柄,则这些是“本地资源”

因此,现在CantStayManaged有两件事情需要清理才能告别。

  1. 托管:成员字段和CLR分配的任何资源。这通常相当于在您的“可处置”成员对象上调用Dispose。
  2. 非托管:我们在其背后执行的所有卑鄙的低级操作。

现在有两种方法可以触发对象清理。

  1. Dispose(true)情况:您在类型上显式调用了Dispose。好程序员。
  2. Dispose(false)情况:您忘记调用Dispose,在这种情况下,终结器应该启动并仍然确保适当的清理。
在这两种情况下,未受管控的资源应该被释放,否则会出现“泄漏”、“崩溃”等问题。但是,在Dispose()前一种情况下,你只应该尝试清理托管资源。在后一种/终结器情况下,CLR可能已经完成并收集了一些成员,因此你不应该访问它们(CLR不能保证对象图的终结顺序)。因此,通过使用if (AmIBeingCalledFromDispose)保护检查来保护你的托管清理,可以避免出现问题。
希望对你有所帮助。

虽然直接持有的本地资源是非托管资源,但完全可以在托管代码中拥有非托管资源。持有“资源”的对象是已要求其他对象代表其执行某些操作,而在此期间可能会对其他对象造成潜在的损害。如果被遗弃的资源无法被垃圾回收器正确清理,则该资源是非托管资源。 - supercat

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