我们在C#中有非托管资源吗?

时间:2011-06-17 08:37:41

标签: c# garbage-collection dispose unmanagedresources

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

根据我的朋友说:

1.a)C#中的每个对象都是托管的,当我们在C#中编码时,没有任何东西像非托管对象或资源。非托管资源概念仅适用于C ++。

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

据我说:

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

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

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

先谢谢。

7 个答案:

答案 0 :(得分:9)

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

然而,这个事实在.NET中隐藏得很好。框架库为这些本机对象提供了很好的包装类。 FileStream,Socket,Thread,Console等。内存也是一个操作系统资源,垃圾收集器是它的包装。

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

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

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

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

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

.NET设计人员认识到了这一需求并设计了IDisposable接口。它的Dispose()方法旨在运行通常由终结器运行的代码,为您提供一种显式释放资源的方法,而不是等待垃圾收集器绕过它。语言设计师通过在他们的语言中添加使用关键字来加入这个潮流,确保自动调用IDisposable.Dispose()。

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

答案 1 :(得分:3)

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

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

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

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

答案 2 :(得分: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类创建的线程都会在进程退出之前停止。

答案 3 :(得分:2)

您在.net Framework下创建的所有内容都是Mananged代码,所以内存仅由框架使用.net框架管理器创建的对象消耗。

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

你可以使用终结器或处理当你想要关闭文件时你需要关闭文件或你正在使用图形并且你想释放与它相关的内存你可以使用这种方法。

答案 4 :(得分:1)

在CLR中创建的所有对象都是由CLR管理的,因此您无需关心它们。

但是,当您开始使用CLR外部的资源(例如COM对象或设备上的锁定)时, 负责释放这些资源。 CLR无法为您执行此操作,但提供IDisposable接口以允许您编写执行清理的代码。

答案 5 :(得分:1)

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

我们必须明确关闭该非托管资源。这是我们在C#中使用Dispose的原因的示例。

答案 6 :(得分:1)

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

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

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