【问题标题】:Do we have Unmanaged resources in C#?我们在 C# 中有非托管资源吗?
【发布时间】:2011-06-17 08:37:40
【问题描述】:

我和朋友讨论了 c# 中的托管和非托管资源。

据我朋友说:

1.a) C# 中的每个对象都是托管的,当我们在 C# 中编写代码时,没有什么比得上非托管对象或资源了。非托管资源概念仅出现在 C++ 中。

1.b) 无论我们在 C++ 中有托管还是非托管资源,我们都需要显式释放它。由于我们在 C# 中有自动垃圾收集器,因此我们无需考虑管理资源。

据我说:

2.a)如果我们没有非托管资源,那么为什么我们需要 C# 中的终结器或 Dispose 方法?

2.b) 垃圾收集器仅包含有关已分配内存的信息,而不是有关资源状态的信息。因此我们需要在 C# 中使用 dispose 方法来释放资源。

我需要帮助来理解上述哪些参数是正确的,以及关于 c# 中非托管资源的信息,它们是否存在?

提前致谢。

【问题讨论】:

  • 你得到答案了吗?如果是,请将其标记为答案。

标签: c# garbage-collection dispose unmanagedresources


【解决方案1】:

不,不使用非托管资源就不可能编写 C# 程序。不可避免的是,C# 程序在 100% 不受管理的操作系统上运行。如果使用文件,则使用操作系统资源。网络连接。一根线。控制台。等等,所有非常不受管理的资源。

然而,这一事实在 .NET 中隐藏得很好。框架库为这些原生对象提供了很好的包装类。文件流、套接字、线程、控制台等。内存也是操作系统资源,垃圾收集器是它的包装器。

在所有这些资源中,只有内存资源是真正自动管理的。他们中的其他人通过他们的包装类获得了一些帮助。它们的终结器是关键,它在调用时释放操作系统资源。这非常接近自动,垃圾收集器注意到包装类对象不再在任何地方被引用,所以它释放它,终结器然后确保非托管资源也被释放。

这通常效果很好,您通常可以在代码中忽略这些实现细节。许多程序员都这样做。

虽然终结器存在问题,但它们需要一段时间才能开始运行。启动它们需要进行垃圾收集,这可能需要几毫秒到几分钟不等。这是不可预测的,它取决于您在代码中消耗内存的速率。如果你不使用很多,那将需要很长时间。

您不能总是等那么久才释放非托管资源。文件就是一个很好的例子。当您打开一个以从文件中读取数据时,您确实应该在完成读取后关闭该文件。如果您等到终结器完成该工作,那么当您需要一段时间后再次打开文件时,您将面临程序失败的风险。您可能通过使用 FileShare.None 打开文件将自己锁定,它也会锁定您自己的代码。没什么大不了的:当你完成阅读时,你调用 Close() 来关闭文件。为了确保它被关闭,您应该将 Close() 调用放在 finally 块中,这样即使代码由于异常而中止,它也会运行。实际上,您显式运行终结器代码。

更严重的情况是操作系统资源非常昂贵。位图就是很好的例子,它们可以占用大量非托管内存或数据库连接,它们有一个池,默认情况下只包含 100 个。对于这些,您可能会让自己陷入这样一种情况,即让终结器负责释放资源只是不起作用,因为它需要太长时间。在终结器可以运行之前,您的程序因异常而死。通常很难诊断,因为这往往只在您的程序处于负载状态时发生。当很多事情发生时,在一台不在您桌面上的机器上,总是很难调试出现的问题。

.NET 设计者意识到了这种需求并设计了 IDisposable 接口。它的 Dispose() 方法旨在运行通常由终结器运行的代码,为您提供一种显式释放资源的方法,而不是等待垃圾收集器处理它。并且语言设计者通过将 using 关键字添加到他们的语言中,从而确保自动调用 IDisposable.Dispose()。

如上所述,在代码中对任何实现 IDisposable 的对象使用 using 或 Dispose() 是可选的,但被许多 .NET 程序员认为是至关重要的。主要是因为每个人都在没有它的情况下开始 .NET 编程,并且在他们的程序变大时迟早会遇到问题。它甚至被规定在调用 Dispose() 没有意义的类上,比如 MemoryStream。当一个类应该实现 IDisposable 但没有实现时,会导致精神上的痛苦,比如 Thread。或者当一个类同时实现 Dispose 和 Close 时(没有区别)。作为比较,Java 有相同的考虑,但没有 IDisposable。

【讨论】:

  • 为了推断您的答案,MemoryStream 不需要但从Stream 继承IDisposable。在这种情况下,更好的继承树将有一个类 ManagedStream 作为MemoryStream 等的基类和Stream(将与IDisposable 一起扩展)来服务作为具有非托管资源的流的基类。
【解决方案2】:

在 .NET 中创建的对象是托管代码,但您的对象可以包含对非托管资源的引用。垃圾收集器 (GC) 确保在托管堆上分配的任何内存在不再需要后被清理。 然而,虽然垃圾收集器非常擅长确保内存不会泄漏,但它对需要释放的其他资源一无所知。例如,垃圾收集器不知道如何关闭文件句柄或如何使用 CoAllocTaskMem 等 API 释放在托管堆外分配的内存

管理这些类型资源的对象必须确保在不再需要它们时释放它们。您可以通过覆盖 System.Object 的 Finalize 方法来完成此操作,这让垃圾收集器知道该对象想要参与其自己的清理(在 C# 中,您使用 C++ 析构函数语法 ~MyObject,而不是直接覆盖该方法)。如果一个类有终结器,那么在收集该类型的对象之前,垃圾收集器将调用对象的终结器并允许它清理它可能持有的任何资源。

这个系统的一个问题是垃圾收集器不能确定地运行,因此,在最后一次引用消失后,您的对象可能很长一段时间都没有最终确定。如果您的对象持有昂贵或稀有的资源,例如数据库连接,这可能是不可接受的。例如,如果您的对象仅打开了 10 个可用连接中的 1 个,则它应该尽快释放该连接,而不是等待垃圾收集器调用 finalize 方法。

为此,您应该实现一个 IDisposable 接口。阅读有关它的更多信息。

【讨论】:

    【解决方案3】:

    a) C# 中的每个对象都是托管的,当我们在 C# 中编写代码时,没有什么比得上非托管对象或资源了。非托管资源概念仅随 C++ 提供。

    这是不正确的。正如其他人所提到的,我们可以从外部 C#(例如 COM)获得非托管资源。

    但是,在 C# 中当然有可能在不访问非托管代码的情况下拥有“非托管资源”。这些资源可能不是严格意义上的垃圾收集不受管理的,但它们是您作为开发人员必须处理清理的资源。以一个线程为例:

    class Foo
    {
        private Thread thread = new Thread(new ThreadStart(DoLotsOfWork));
        private AutoResetEvent endThread = new AutoResetEvent(false);
        private int sum = 0;
    
        public Foo()
        {
            thread.Start();
        }
    
        public StopThread()
        {
            endThread.Set();
        }
    
        private void DoLotsOfWork()
        {
            while (!endThread.WaitOne(1000))
            {
                sum += 1;
            }
        }
    }
    
    static void Main(string[] args)
    {
        Foo foo = new Foo();
        // Additional code...
        foo.StopThread();
    }
    

    假设附加代码返回或抛出异常。如果你不显式调用 StopThread,执行 DoLotsOfWork 的线程不会结束,你的进程很可能不会退出。

    b) 无论我们在 C++ 中有托管还是非托管资源,我们都需要显式释放它。由于我们在 C# 中有自动垃圾收集器,因此我们无需考虑管理资源。

    我们绝对必须考虑在 C# 中管理资源。正如您所建议的,这就是 IDisposable 存在的原因。

    考虑对上述代码进行这样的修改:

    class Foo : IDisposable
    {
        private bool disposed = false;
        private Thread thread = new Thread(new ThreadStart(DoLotsOfWork));
        private AutoResetEvent endThread = new AutoResetEvent(false);
        private int sum = 0;
    
        public Foo()
        {
            thread.Start();
        }
    
        public StopThread()
        {
            endThread.Set();
        }
    
        public Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }
    
        private void DoLotsOfWork()
        {
            while (!endThread.WaitOne(1000))
            {
                sum += 1;
            }
        }
    
        private void Dispose(bool disposing)
        {
            if (!disposed && disposing)
            {
                StopThread();
                disposed = true;
            }
        }
    }
    
    static void Main(string[] args)
    {
        using (Foo foo = new Foo())
        {
            // Additional code...
        }
    }
    

    现在我们可以确定,无论附加代码做什么,Foo类创建的线程都会在进程退出之前停止。

    【讨论】:

      【解决方案4】:

      您在 .net 框架下创建的每件事都是托管代码,因此内存由仅由框架使用 .net 框架管理器创建的对象消耗。

      所有在 .net 框架之外创建的东西都是非托管代码。

      当您想要释放重物时,您可以使用终结器或处置,例如您需要关闭文件,或者您正在使用图形并且您想要释放与其关联的内存,您可以使用此方法。

      【讨论】:

      • 托管和非托管代码的概念在很大程度上与托管和非托管资源的概念是正交的。托管资源是应该手动清理的对象,但如果被放弃,最终将(通过 Finalize)执行必要的清理。非托管资源是一个实体(几乎可以是任何东西),它需要某种形式的手动清理,如果简单地放弃它就不会发生。虽然除了托管代码之外不能拥有托管资源,但非托管资源可以存在于任何地方,即使在 100% 托管代码中也是如此。
      【解决方案5】:

      确实,在CLR中创建的所有对象都是由CLR管理的,所以你不需要关心它们。

      但是,当您开始使用 CLR 外部的资源(例如 COM 对象或设备上的锁)时,有责任释放这些资源。 CLR 不能为你做这件事,但它提供了IDisposable 接口让你编写代码来进行清理。

      【讨论】:

        【解决方案6】:

        我们确实必须处理 .NET 中的非托管资源。一个很好的例子是与数据库的连接。

        我们必须明确关闭该非托管资源。这是为什么我们在 C# 中使用 Dispose 的一个示例。

        【讨论】:

          【解决方案7】:

          拥有非托管资源的对象会将其他实体置于某种不受欢迎的状态(例如,使它们无法用于其他目的),直到被告知不再需要它为止。如果它在没有事先被告知不再需要的情况下被放弃,那些其他实体将处于不受欢迎的状态。

          托管资源是一个实体,它类似地将其他实体置于某种不受欢迎的状态,直到被告知不再需要它,但如果所有“故意”对它的引用都被放弃,它最终会自动清理自己。

          来自长寿命对象的事件订阅完全存在于托管代码中,但由于它们不会在长寿命对象的生存期内自动清理,因此应将其视为非托管资源。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2013-02-02
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2015-05-27
            • 1970-01-01
            相关资源
            最近更新 更多