visual C#(十四)使用垃圾回收和资源管理

   日期:2020-05-12     浏览:94    评论:0    
核心提示:参考书:《 visual C# 从入门到精通》第二部分 理解C#对象模型第14章 使用垃圾回收和资源管理文章目录14.1 对象的生存期14.1.1 编写析构器14.1.2 为什么要使用垃圾回收器14.1.3 垃圾回收器的工作原理14.1.4 慎用析构器14.2 资源管理14.2.1 资源释放方法14.2.2 异常安全的资源清理14.2.3 `using`语句和`IDisposable`接口14.2.4 从析构器中调用`Dispose`方法14.3 实现异常安全的资源清理14.1 对象的生存期我们知c#

参考书:《 visual C# 从入门到精通》
第二部分 理解C#对象模型
第14章 使用垃圾回收和资源管理

文章目录

    • 14.1 对象的生存期
      • 14.1.1 编写析构器
      • 14.1.2 为什么要使用垃圾回收器
      • 14.1.3 垃圾回收器的工作原理
      • 14.1.4 慎用析构器
    • 14.2 资源管理
      • 14.2.1 资源释放方法
      • 14.2.2 异常安全的资源清理
      • 14.2.3 `using`语句和`IDisposable`接口
      • 14.2.4 从析构器中调用`Dispose`方法
    • 14.3 实现异常安全的资源清理

14.1 对象的生存期

我们知道,创建对象要用new关键字,

Square mysquare = new Square();

new实际上是分两步的:

  1. new操作从堆中分配原始内存,这个阶段无法进行干预

  2. new操作将原始内存转换成对象:它必须初始化对象。可用构造器控制这一阶段

当变量mysquare离开作用域时,它引用的对象就没有引用了,这样对象会被销毁,占用的内存被回收。对象的销毁也分来两步走:

  1. `CLR`执行清理工作,可以写一个析构器来控制
  2. `CLR`将对象占用的内存归还给堆,解除对象内存的分配。对这个阶段我们没有控制权。

销毁对象并将内存归还给堆的过程称为垃圾回收

14.1.1 编写析构器

用析构器可以在对象被垃圾回收时执行必要的清理。CLR可以自动清理对象使用的任何托管,所以很多时候时不需要自己写析构器的。但要是托管资源很大(如多维数组),就可以考虑对该资源的所有引用都设为null,使资源能被立即清理。如果对象引用了非托管资源,析构器就更有用了。

构造器的语法是~+类名,如下:

class FileProcessor{
    FileStream file=null;
    public FileProcessor(string fileName){
        this.file=File.OpenRead(fileName);
    }
    ~FileProcessor(){
        this.file.Close();
    }
}

注意以下几点:

  • 构造器只适合引用类型。值类型不能声明构造器(如struct
  • 不能为构造器指定访问修饰符。因为总是由垃圾回收器来调用
  • 析构器不能获取任何参数

编译器内部自动将构造器转换成对Object.Finalize方法的一个重写版本的调用。如:

class FileProcessor{
    -FileProcessor(){//你的代码}
}

编译器转换成如下的形式:

class FileProcessor{
    protected override void Finalize(){
        try{//你的代码
        }
        finally{base.Finalize();}
    }
}

14.1.2 为什么要使用垃圾回收器

C#中我们总是不能自己销毁对象,而是由CLR在它认为合适的时间帮你做这件事。由于一个对象可能会有多个引用,CLR必须跟踪所有的引用。对象的生存期不能和特定的引用变量绑定。只有在一个对象的所有引用都消失后才可以销毁该对象,回收其内存。

如果由程序员负责销毁对象,很有可能会遇到以下的情况:

  • 忘记销毁对象。这样析构器不会运行,清理工作也不会运行,内存不会回到堆,最终内存会被消耗完
  • 试图销毁活动对象,造成一个或多个变量容纳对已销毁对象的引用,即虚悬引用
  • 试图多次销毁同一个对象

而垃圾回收器可以做到以下几点担保:

  • 每个对象都会被销毁
  • 每个对象只能被销毁一次
  • 每个对象只有在它不可达时销毁

注意,垃圾回收并不是在对象不再需要时立即进行。垃圾回收是一个代价较高的过程,所以只在觉得有必要时才会进行垃圾回收。

可以通过静态方法System.GC.Collect在程序中调用垃圾回收器(回收过程时异步发生的),但不建议这样做。

14.1.3 垃圾回收器的工作原理

垃圾回收器在它自己的线程中运行。它运行时应用程序中的其他线程将暂停,因为垃圾回收器可能需要移动对象并更新对象引用。

它的步骤大体如下:

  1. 构造所有可达对象的一个映射,它会反复跟随对象中的引用字段
  2. 检查是否由任何不可达对象包含一个需要运行的析构器,需终结的任何不可达对象都放到一个称为freachable的特殊队列中
  3. 回收剩下的不可达对象,它会在堆中向下面移动可达的对象,对堆进行“碎片整理”,释放位于堆顶部的内存。一个可达对象被移动之后会跟新对该对象的所有引用
  4. 然后允许其他线程恢复执行
  5. 在一个独立的线程中,对需要终结的不可达对象执行终结操作、

14.1.4 慎用析构器

一个类中包含析构器会使代码和垃圾回收过程变得复杂,同时会影响程序的运行速度。如果程序中不包含析构器,垃圾回收器就不需要将不可达对象放到freachable队列并对他们进行“终结”。所以除非确实有必要,最好尽量避免使用析构器。同时要确定析构器不相互依赖或相互重叠。

14.2 资源管理

有点资源比较宝贵,用完后应该马上释放,这时就需要通过自己写的资源清理方法亲自释放资源。显式调用类的资源清理方法控制资源是释放的时机。

14.2.1 资源释放方法

比如来自System.IO命名空间中的TextReader类,该类提供从顺序输入流中读取字符的机制。TextReader包含虚方法Close,它负责关闭流,这是一个资源清理方法。StreamReader类从流中读取字符,StringReader类从字符串中读取字符。这两个类都从TextReader类派生,都重写了Close方法。如使用StreamReader类从文件中读取文本行并在屏幕上显示:

TextReader reader =new StreamReader(filename);
string line;
while((line=reader.ReadLine())!=null){
    Console.WriteLine(line);
}
reader.Close();

上述代码的非常重要的一点是调用Close方法来释放文件句柄以及相关资源。但有一个问题是它不是异常安全的。如果对ReadLineWriteLine方法的调用抛出异常,对Close的调用就不会发生了,这样可能会耗尽文件句柄资源,无法打开更多文件。

14.2.2 异常安全的资源清理

为了解决上面的问题,可以在finally块中调用Close方法:

TextReader reader=new StreamReader(filename);
try{
    string line;
    while((line=reader.ReaderLine())!=null){
        Console.WriteLine(line);
    }
}
finally{
    reader.Close();
}

但它存在几个缺点:

  • 如果要释放多个资源,局面就会很混乱,可能出现嵌套的tryfinally
  • 有时需要修改代码来适应这一惯用法
  • 它不能创建解决方案的一个抽象
  • 对资源的引用保留在finally块之后的作用域中,这意味着可能不小心使用一个已释放的资源

14.2.3 using语句和IDisposable接口

using语句提供了一个脉络清晰的机制来控制资源的生存期。语法如下:

using(type variable=initialzation){
    ...;
}

如:

using(TextReader reader=new StreamReader(filename)){
    string line;
    while((line=reader.ReadLine())!=null){
        Console.WriteLine(line);
    }
}

它完全等价于前面用finally块的形式:

{
    TextReader reader=new StreamReader(filename);
try{
    string line;
    while((line=reader.ReaderLine())!=null){
        Console.WriteLine(line);
    }
}
finally{
    reader.Close();
}
}

using语句定义了一个作用域。

using语句声明的类型必须实现IDisposable接口。IDisposableSystem命名空间中,只包含一个名为Dipose的方法。

namespace System{
    interface IDisposable{
        void Dispose();
    }
}

StreamReader类实现了这个接口,它的Dispose方法会调用Close方法来关闭流。using语句解决了finally块可能出现的所有问题。

14.2.4 从析构器中调用Dispose方法

实现IDisposable接口的示例如下:

Class Example:IDisposable{
    private Resource scarce;
    private bool dispose=false;
    ...;
    ~Example(){
        this.Dispoable(false);
    }
    public virtual void Dispose(){
        this.Dispose(true);
        GC.SuppressFinalize(this);
    }
    protected virtual void Dispose(bool disposing){
        if(!this.disposed){
            if(disposing){
                ...;
            }
            this.disposed=true;
        }
    }
    public void SomeBehavior(){
        checkIfDisposed();
        ...;
    }
    private void checkIfDisposed(){
        if(this.disposed){
            throw new ObjectDisposedException("示例:对象已经清理")}
    }
}

注意:

  • 受保护的Dispose方法可以安全的多次调用。变量disposed指出方法以前是否运行过,这样防止在并发调用方法时资源被多次清理。方法只有在第一次运行才会清理资源
  • 受保护的Dispose方法支持托管资源和非托管资源的清理
  • 公共Dispose方法调用静态GC.SuppressFinalize方法。该方法阻止垃圾回收器为这个对象调用析构器,因为对象已经终结了
  • 类的所有常规方法都要检查对象是否已经被清理,是,就抛出异常

14.3 实现异常安全的资源清理

最后放上一个using语句的示例:

Calculator.cs

using System;
using System.Collections.Generic;
using System.Text;

namespace C_14_3
{
    class Calculator:IDisposable
    {
        private bool disposed = false;
        public int Divide(int first,int second)
        {
            return first / second;
        }

        public void Dispose()
        {
            if (!disposed)
            {
                Console.WriteLine("Calculator being disposed");
            }
            this.disposed = true;
            GC.SuppressFinalize(this);
        }

        public Calculator()
        {
            Console.WriteLine("Calculator being created");
        }
        ~Calculator()
        {
            Console.WriteLine("Calculator being finalized");
            this.Dispose();
        }
    }
}

Program.cs

using System;

namespace C_14_3
{
    class Program
    {
        static void Main(string[] args)
        {
            
            using(Calculator calculator1=new Calculator())
            {
                Console.WriteLine($"120/0={calculator1.Divide(120, 0)}");
            }
               Console.WriteLine("Program finishing");
            
        }
    }
}

using语句中故意引发异常,观察结果,程序最后还是调用了Dispose方法来回收资源,运行结果为

 
标签: 文章标签: c#
打赏
 本文转载自:网络 
所有权利归属于原作者,如文章来源标示错误或侵犯了您的权利请联系微信13520258486
更多>最近资讯中心
更多>最新资讯中心
更多>相关资讯中心
0相关评论

推荐图文
推荐资讯中心
点击排行
最新信息
新手指南
采购商服务
供应商服务
交易安全
关注我们
手机网站:
新浪微博:
微信关注:

13520258486

周一至周五 9:00-18:00
(其他时间联系在线客服)

24小时在线客服