什么是非托管资源?

194
我想了解未经管理的资源。 请问是否有人能够给我一个基本概念?

1
另外请参阅此页面,其中提供了关于IDisposable的正确使用方式以及如何考虑未管理资源的绝佳解释和模式:https://dev59.com/K3RB5IYBdhLWcg3wuZU1 - Kyle Baran
8个回答

217

被管理的资源基本上指的是由垃圾收集器管理的"受管内存"。当你不再引用使用受控内存的管理对象时,垃圾回收器将(最终)释放该内存。

未被管理的资源是垃圾收集器不知道的所有内容。例如:

  • 打开的文件
  • 打开的网络连接
  • 非托管内存
  • 在XNA中:顶点缓冲区、索引缓冲区、纹理等。

通常在失去对管理它们的对象的所有引用之前,您希望释放这些未被管理的资源。通过调用该对象上的Dispose或(在C#中)使用using语句来处理为您调用Dispose

如果您没有正确地Dispose未经管理的资源,则垃圾回收器最终将在包含该资源的对象被垃圾回收时为您处理它(这是“终结”)。但是,由于垃圾回收器不知道未经管理的资源,因此它无法告诉自己需要多么严重地释放它们-因此,您的程序可能表现不佳或完全耗尽资源。

如果您自己实现了处理未经管理的资源的类,则由您负责正确实现DisposeFinalize


9
开放式数据库连接属于哪个类别?托管/非托管?开放式数据库连接属于非托管类别。 - Deviprasad Das
9
其他回答忽略了一个重要点,你正在对一个托管对象调用Dispose方法,该对象内部处理封装的非托管资源(例如文件句柄、GDI+位图等)的释放。如果你直接访问非托管资源(如PInvoke等),则需要自行处理释放。 - Ian Mercer
3
@Dev: 如果您没有使用某种假设的非托管内存数据库,则未受托管-因为垃圾回收器不知道它。但是连接对象本身可能并不持有未受管资源。 假定数据库连接在某个地方使用打开的文件或网络连接,但是有可能另一个对象(连接对象之外的其他对象)正在处理该未受管资源(也许您的数据库库缓存连接)。请查看文档,并查看要求您何时调用Dispose或使用using的位置。 - Andrew Russell
12
我对此有一个基本的评论/问题,我能否通过类型将对象归类为托管/非托管的,例如,string是托管的,DataSet是非托管的(这就是为什么它有一个Dispose()方法),数据库连接是非托管的(因为它们有dispose),所以假设是:如果它有“Dispose()”方法,则它是非托管的?除此之外,XmlDocument对象是什么?谢谢。 - ganders
18
这是一个不错的经验法则。但要注意,所有C#类实例都是托管对象。如果一个类的实例可能持有非托管资源,则该类应该实现 IDisposable 接口。如果一个类确实实现了IDisposable接口,那么在使用完这些实例后,你应该使用 usingDispose() 进行释放。基于此,你可以得出相反的结论:如果一个类实现了 IDisposable 接口,那么它很可能在内部持有非托管资源。 - Andrew Russell
显示剩余3条评论

84
一些用户将打开的文件、数据库连接、已分配的内存、位图和文件流等资源归类为托管资源,而另一些人则将它们归类为非托管资源。所以它们是托管还是非托管?
我认为,答案更加复杂:在.NET中打开文件时,您可能会使用一些内置的.NET类System.IO.File、FileStream或其他类。因为它是一个普通的.NET类,所以它是托管的。但它是一个包装器,内部执行“肮脏的工作”(使用Win32 dll与操作系统通信,调用底层函数甚至汇编指令),真正打开文件的操作是不被.NET知道的,也就是非托管的。
但是,您可以通过使用汇编指令自己打开文件,并绕过.NET文件函数。那么句柄和打开的文件就是非托管资源。
同样的情况也适用于数据库:如果您使用某个数据库程序集,那么您将拥有DbConnection等类,它们对.NET是已知的并且是托管的。但是它们包装了“肮脏的工作”,也就是非托管的(在服务器上分配内存,建立与之的连接等)。
如果您不使用这个包装器类并自己打开某个网络套接字并使用一些命令与自己的奇怪数据库通信,那么这就是非托管的。
这些包装器类(File、DbConnection等)是托管的,但它们内部使用非托管资源的方式与您相同,如果您不使用包装器并自己完成“肮脏的工作”。因此,这些包装器确实实现了Dispose/Finalize模式。当包装器不再需要时,它们负责允许程序员释放非托管资源,并在垃圾回收器对包装器进行垃圾回收时释放它们。包装器将被垃圾回收器正确地垃圾回收,但其中的非托管资源将通过使用Dispose/Finalize模式来收集。
如果您不使用内置的.NET或第三方包装器类,并且在您的类中使用某些汇编指令打开文件,则这些打开的文件是非托管的,而且您必须实现dispose/finalize模式。如果不这样做,将会出现内存泄漏、永久锁定资源等问题,即使您不再使用它(文件操作已完成)或甚至在应用程序终止之后也会出现这些问题。

使用这些包装器时,您也有责任。对于那些实现了 dispose/finalize(您可以通过它们是否实现 IDisposable 接口来识别它们)的包装器,请实现自己的 dispose/finalize 模式,并在销毁这些包装器时调用它们的 Dispose 方法或者给它们发送信号以释放它们的非托管资源。如果不这样做,这些资源将在一段不确定的时间后被释放,但最好立即释放它们(立即关闭文件而不是让它保持打开状态并占据几分钟/几小时)。因此,在您的类的 Dispose 方法中,调用所有已使用包装器的 Dispose 方法。


2
关于“托管资源与非托管资源”的额外澄清,这是一个很好的解释。 - now he who must not be named.
2
这很简单。在使用每个类之前,您必须验证它是否实现了IDisposable接口。如果是,则如果您在一个方法中使用这样的类(例如:打开文件,存储文本,关闭文件),则可以使用using(){}模式,该模式会自动调用Dispose。如果您在多个方法中使用此类(例如:您的类包含File,在构造函数中打开文件,然后几个方法添加一些日志...),则必须通过您的类实现IDisposable接口,实现Dispose/Finalize模式并正确处理该类的对象。 - Marťas
1
由于它是一个普通的.NET类,因此一些内置的.NET类如System.IO.File、FileStream或其他类被认为是托管的。但是,这是错误和误导性的。它们不是托管的。如果它们是托管的,那么你可以分配这些类并期望垃圾回收器以确定性方式完全处理所有资源的释放。然而,这将导致文件句柄和非托管资源被锁定并保持更长时间,因为垃圾回收器不会释放该类,并且可能不会对其进行最终处理。 - AaronLS
@Martas "它们将被妥善地垃圾回收。如果您失去了对FileStream的引用,它将被垃圾回收。" 这是这些类的常见误用。是的,垃圾收集器将释放未受管控的资源,但是这种方式会有很长时间的延迟,因为它只有在内存压力需要时才会进行解除分配,这可能需要很长时间。按照您的建议编写的应用程序的行为将是具有许多错误的行为,例如在不再需要它们之后长时间锁定文件,使用过多不必要的连接来超载数据库等。 - AaronLS
1
@AaronLS 在你的评论中,你提到了“FileStream”,并将其称为非托管资源,但实际上它不是。尽管在内部使用非托管资源来完成工作。在托管世界中,微软通过实现Dispose模式来隐藏了许多非托管内容。托管代码并不意味着它不使用非托管资源。然而,微软在这些类型的对象上实现了IDisposable,做得很好。这可以从它实现IDisposable的事实中看出。基于这一证据,我们应该将其视为托管对象。 - Malik Khalil
显示剩余10条评论

18

未托管资源是指在.NET运行时(CLR)之外运行的资源(也称非.NET代码)。例如,调用Win32 API中的DLL或调用用C++编写的.dll。


7
一个“未托管的资源”不是一个物体,而是一种责任。如果一个对象拥有一个未托管的资源,那意味着(1)某个实体在它外部被操纵,如果不清理可能会引起问题,(2)该对象具有执行此类清理所需的信息,并负责执行它。
虽然许多类型的未托管资源非常强烈地与各种类型的操作系统实体相关联(文件、GDI 句柄、分配的内存块等),但除了清理责任之外,没有单个类型的实体是所有这些实体共享的。通常,如果一个对象有责任执行清理,它将具有一个 Dispose 方法,指示它执行其负责的所有清理。
在某些情况下,对象将允许可能会在没有人调用 Dispose 的情况下被放弃。GC 允许对象请求通知它们已被放弃(通过调用一个名为 Finalize 的例程),并且对象可以使用此通知自行执行清理。
像“托管资源”和“未托管资源”这样的术语不幸地被不同的人用来表示不同的含义;坦白地说,认为根据对象是否具有任何清理责任、是否只有在调用 Dispose 时才会处理清理责任或者是否应通过 Dispose 处理清理责任,但也可以通过 Finalize 处理清理责任更有用。

6
托管资源和非托管资源的基本区别在于垃圾回收器了解所有托管资源,在某个时刻,GC会清理与托管对象相关联的所有内存和资源。GC不知道非托管资源,比如文件、流和句柄,所以如果您没有在代码中显式清理它们,那么就会出现内存泄漏和锁定资源的情况。
以上内容摘自此处,欢迎阅读整篇文章。

2
任何在.NET托管堆中分配内存的资源都是托管资源。CLR完全了解这种类型的内存,并将尽一切努力确保其不会变成孤儿。其他任何东西都是非托管的。例如,与COM进行交互可能会在进程内存空间中创建对象,但CLR不会对此负责。在这种情况下,跨托管边界进行调用的托管对象应该对其之外的任何事情负责。

1
首先,让我们了解VB6或C ++程序(非Dotnet应用程序)的执行方式。我们知道计算机只能理解机器级代码。机器级代码也称为本地或二进制代码。因此,当我们执行VB6或C ++程序时,相应的语言编译器将语言源代码编译为本地代码,然后可以被底层操作系统和硬件理解。
本地代码(非托管代码)是特定于生成它的操作系统的。如果您将这个已编译的本地代码尝试在另一个操作系统上运行,它将失败。因此,这种程序执行风格的问题在于,它无法从一个平台移植到另一个平台。
现在让我们了解一下.NET程序的执行方式。使用dotnet我们可以创建不同类型的应用程序。一些常见的.NET应用程序类型包括Web、Windows、Console和Mobile应用程序。无论应用程序的类型如何,当您执行任何.NET应用程序时,以下情况发生。
  1. .NET应用程序被编译成中间语言(IL)。IL也被称为公共中间语言(CIL)和Microsoft中间语言(MSIL)。.NET和非.NET应用程序都会生成一个程序集。程序集的扩展名为.DLL或.EXE。例如,如果您编译Windows或控制台应用程序,您将得到一个.EXE文件,而当我们编译Web或类库项目时,我们得到一个.DLL文件。.NET程序集与非.NET程序集之间的区别在于,DOTNET程序集是以中间语言格式存在的,而NON DOTNET程序集是以本机代码格式存在的。

  2. NON DOTNET应用程序可以直接运行在操作系统之上,而DOTNET应用程序运行在称为公共语言运行时(CLR)的虚拟环境之上。CLR包含一个组件叫做即时编译器(JIT),它将把中间语言转换为本机代码,使底层操作系统能够理解。

因此,在.NET中,应用程序执行包括两个步骤: 1. 语言编译器将源代码编译为中间语言(IL) 2. CLR中的JIT编译器将IL转换为本机代码,然后可以在底层操作系统上运行。

由于.NET程序集(assembly)采用中间语言格式,而不是本地代码,因此只要目标平台有公共语言运行时(CLR),.NET程序集就可以在任何平台上移植。目标平台的CLR将中间语言转换为底层操作系统能够理解的本地代码。中间语言也称为托管代码,这是因为CLR管理其中运行的代码。例如,在VB6程序中,开发人员负责释放对象所占用的内存。如果程序员忘记释放内存,可能会遇到难以检测的内存溢出异常。另一方面,.NET程序员不需要担心释放对象所占用的内存。CLR提供了自动内存管理,也称为垃圾回收。除了垃圾回收外,CLR还提供了其他几个好处,我们将在后面的课程中讨论。由于CLR管理和执行中间语言(IL),因此它(IL)也被称为托管代码。
.NET支持不同的编程语言,如C#、VB、J#和C ++。C#、VB和J#只能生成托管代码(IL),而C ++可以生成托管代码(IL)和非托管代码(本机代码)。
本机代码不会永久存储在任何地方,当我们关闭程序时,本机代码就被丢弃了。当我们再次执行程序时,本机代码会再次生成。
.NET程序类似于Java程序的执行。在Java中,我们有字节码和JVM(Java虚拟机),而在.NET中,我们有中间语言和CLR(公共语言运行时)。
这是从此链接提供的 - 他是一个很好的导师。 http://csharp-video-tutorials.blogspot.in/2012/07/net-program-execution-part-1.html

0

非托管和受管资源都基于应用程序域

依据我的理解,非托管资源是指用于连接应用程序域外部的一切资源。 例如:你使用HttpClient类获取域外数据,或使用FileStream读写文件。

我们使用 Using 代码块及时释放这些类对象,因为垃圾回收器(GC)首先负责处理进程内资源而不是外部资源,虽然它最终也会将其处理掉。


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