2010-08-17 55 views
5

Vui lòng xem mã bên dưới. Tôi hy vọng nó sẽ in 10 hoặc 10 vì tôi đã gọi một cách rõ ràng bộ thu gom rác. Nhưng tôi luôn nhận được 0 hoặc 20 là đầu ra. Tại sao vậy?C# Destructor không hoạt động như mong đợi

void Main() 
{ 
    Panda[] forest_panda = new Panda[10]; 
    for(int i=0; i<forest_panda.GetLength(0);i++) 
    { 
     forest_panda[i]=new Panda("P1"); 
    } 

    for(int i=0; i<forest_panda.GetLength(0);i++) 
    { 
     forest_panda[i]=new Panda("P1"); 
    } 

    System.GC.Collect(); 

    Console.WriteLine("Total Pandas created is {0}",Panda.population);   
} 

class Panda 
{ 
    public static int population=0; 
    public string name; 

    public Panda(string name) 
    { 
     this.name = name; 
     population = population + 1; 
    } 

    ~Panda() 
    { 
     population = population - 1; 
    } 
} 

Xin lưu ý rằng lớp dành cho Main được tạo tự động bởi LINQPad (trình chỉnh sửa đi kèm với cuốn sách "C# 4.0 in a Nutshell"). Tôi mới đến C#.

+1

http://blogs.msdn.com/b/oldnewthing/archive/2010/08/09/10047586.aspx Hiện tại, nhiều trang đẹp hơn nữa ở trang đầu của blog của Raymond. –

+1

tại sao bạn mong đợi nó in 10 khi bạn tạo 20 Pandas? –

+0

@Rune FS Tôi không mong đợi bộ thứ hai của 10 Pandas được GCed. nhưng bây giờ tôi hiểu rằng nó cũng có thể được GCed vì nó không còn được tham chiếu trong chương trình khi tôi gọi là GC. – Manoj

Trả lời

7

Bạn chưa chạy bộ sưu tập rác. Từ tài liệu của GC.Collect():

Sử dụng phương pháp này để tìm cách lấy lại tất cả bộ nhớ không thể truy cập được. Tuy nhiên, phương pháp Thu thập không đảm bảo rằng tất cả bộ nhớ không thể truy cập được khai hoang.

Tất cả các đối tượng, bất kể khoảng thời gian chúng đã có trong bộ nhớ, là được xem xét để thu thập; tuy nhiên, các đối tượng được tham chiếu trong mã được quản lý không được thu thập. Sử dụng phương thức này để buộc hệ thống cố gắng để xác nhận lại số tiền tối đa là bộ nhớ khả dụng.

Bộ thu thập garabage được tối ưu hóa cao và "tự quyết định" khi thực sự thu gom rác và sau đó gọi cho finalizers. Ngoài ra, tất cả được thực hiện không đồng bộ. Đó cũng là lý do tại sao Finalizers được gọi là dọn dẹp không xác định. Bạn không bao giờ bây giờ khi dọn dẹp xảy ra.

Bây giờ bạn có hai tùy chọn. Bạn có thể gọi GC.WaitForPendingFinalizers() mà sẽ dừng chuỗi hiện tại cho đến khi tất cả các đối tượng cuối cùng đã được hoàn thành. Hoặc gọi quá tải mới này: System.GC.Collect(int generation, System.GCCollectionMode mode) với GCCollectionMode.Forced Nó đã được giới thiệu trong .NET 3.5.

Chỉ cần nhớ rằng thông thường không cần thiết và quan trọng hơn: ý tưởng tồi để gọi bộ thu gom rác theo cách thủ công. Ngoài ra việc thực hiện finalizer chỉ cần thiết trong những dịp hiếm hoi. Gọi bộ thu gom rác sẽ làm chậm thời gian chạy. Triển khai finalizers sẽ làm chậm thời gian chạy bổ sung. Bộ sưu tập garabge đặt tất cả các đối tượng thực hiện finalizer vào hàng đợi finalization khi họ đã sẵn sàng để được garabge thu thập. Xử lý hàng đợi này là tốn kém. Để làm cho mọi việc trở nên tồi tệ hơn, khi trình finalizer được chạy, nó không đảm bảo rằng các thành viên bạn đang cố truy cập vẫn còn sống. Nó là rất tốt có thể họ đã được grabage thu thập. Đó là lý do tại sao bạn chỉ nên sử dụng trình hoàn thành khi bạn có các tài nguyên không được quản lý cần được dọn dẹp.

Tất cả điều này là chắc chắn không cần thiết trong ví dụ của bạn. Những gì bạn acutally muốn là IDisposable để xác định dọn dẹp.

+0

Bởi vì đôi khi người thu gom rác "quyết định" rằng đây là thời điểm tốt để thu gom rác và may mắn là tất cả các bộ phận xử lý đang chờ xử lý đã được thực hiện. – bitbonk

+0

cảm ơn rằng làm việc – Manoj

2

Bạn tạo hai mươi đối tượng, vì vậy giá trị sau đó sẽ là 20. Gọi rõ ràng System.GC.Collect() không thực sự đảm bảo gọi hàm hủy. Vì vậy, nếu nó được gọi là tất cả 20 đối tượng có thể đã bị phá hủy hoặc không ai có thể có được.

Điều này giải thích điều gì đang thực sự xảy ra.

Thực tiễn không tốt là tạo trình phá hủy hoặc gọi GC.Collect một cách rõ ràng.

Nếu Một đối tượng cần làm dọn dẹp, cần thực hiện IDisposable

6

Có một vài điều cần chú ý ở đây:

Trước hết GC cư xử khác nhau giữa phát hành và debug xây dựng. Nói chung, trong các đối tượng chế độ phát hành có thể được khai hoang sớm hơn trong chế độ gỡ lỗi.

Vì Tim chỉ ra gọi GC.Collect không gọi finalizers. Nếu bạn muốn đợi cho finalizers để chạy GC.WaitForPendingFinalizers gọi là tốt.

Trình hoàn thành được điều hành bởi một chuỗi chuyên dụng, vì vậy, bạn đang thực sự sửa đổi trạng thái từ hai luồng khác nhau mà không cần đồng bộ hóa. Trong khi điều này có thể không phải là một vấn đề trong trường hợp cụ thể này, làm như vậy không phải là một ý tưởng tốt. Nhưng trước khi bạn đi và thêm đồng bộ hóa vào finalizer của bạn, hãy nhớ rằng một finalizer deadlocked có nghĩa là không có finalizers nào sẽ chạy và do đó bộ nhớ cho các đối tượng đó sẽ không được khai hoang.

+0

Tôi không bao giờ biết có thể có một vấn đề đồng bộ hóa ở đây! Chủ đề mà không có tôi tạo ra bất cứ điều gì! Nó chỉ là một vấn đề khi sử dụng finalizer hoặc tôi phải tìm cho họ bất cứ khi nào sử dụng các biến tĩnh? – Manoj

+0

Vấn đề trong trường hợp này là bởi vì finalizers được chạy bởi một thread chuyên dụng được tạo ra bởi thời gian chạy. –

1

Trong .NET, thời gian tồn tại của đối tượng là non-deterministic và không hoạt động như bạn mong đợi từ hàm tạo/phá hủy C++. Trong thực tế, các đối tượng .NET không có các hàm hủy. Trình hoàn thiện khác với dự kiến ​​sẽ dọn sạch các tài nguyên không được quản lý được đối tượng sử dụng trong suốt thời gian tồn tại của nó.

Để có cách xác định giải phóng tài nguyên được sử dụng bởi đối tượng của bạn, bạn triển khai giao diện IDisposable. IDisposable không phải là hoàn hảo mặc dù vì nó vẫn yêu cầu mã gọi để xử lý chính xác đối tượng khi nó được thực hiện, và thật khó để xử lý nhiều cuộc gọi vô tình để vứt bỏ. Tuy nhiên cú pháp trong C# làm cho việc này nói chung rất dễ dàng.

class Panda : IDisposable 
{ 
    public static int population = 0; 
    public string _name; 

    public Panda(string name) 
    { 
     if(name == null) 
      throw new ArgumentNullException(name); 

     _name = name; 
     population++; 
    } 

    protected virtual void Dispose(bool disposing) 
    { 
     if(disposing && name != null) 
     { 
      population--; 
      name = null; 
     } 
    } 

    public void Dispose() 
    { 
     Dispose(true); 
     GC.SuppressFinalize(this); 
    } 

    ~Panda(){ Dispose(false); } 
} 

Sau đó, để sử dụng lớp:

using(var panda = new Panda("Cute & Cuddly")) 
{ 
    // Do something with the panda 

} // panda.Dispose() called automatically 
+0

Vâng tôi đã suy nghĩ dọc theo dòng của C++ destructor và do đó đã nhầm lẫn. Cảm ơn.. – Manoj

1

Sử dụng hàm hủy (finalizers a.k.a.) không phải là thực sự là một cách tốt để làm việc trong C#. Không có gì đảm bảo rằng finalizer sẽ chạy, ngay cả khi bạn gọi một cách rõ ràng bộ thu gom rác. Bạn không nên cố gắng ép buộc thu gom rác thải, vì nó có thể sẽ có tác động tiêu cực đến ứng dụng của bạn. Thay vào đó, nếu bạn cần giải phóng tài nguyên thuộc sở hữu của một đối tượng, bạn nên triển khai giao diện IDisposable và đặt logic dọn dẹp của bạn bên trong phương thức Dispose(). Ngược lại, khi bạn sử dụng một đối tượng thực hiện IDisposable, bạn nên luôn chú ý gọi phương thức Dispose() của nó khi bạn kết thúc với nó. C# cung cấp câu lệnh "using" cho mục đích này.

Nhiều lớp làm I/O (chẳng hạn như Luồng) triển khai IDisposable. Đây là một ví dụ về cách sử dụng một FileStream để đọc một tập tin văn bản. Lưu ý "sử dụng" tuyên bố để đảm bảo FileStream được xử lý khi chúng ta đã kết thúc với nó:

using (FileStream fs = File.OpenRead("C:\\temp\\myfile.txt")) 
{ 
    // Read a text file 1024 bytes at a time and write it to the console 
    byte[] b = new byte[1024]; 
    while (fs.Read(b, 0, b.Length) > 0) 
    { 
     Console.WriteLine(Encoding.UTF8.GetString(b)); 
    } 
} // Dispose() is called automatically here 

Đoạn mã trên là tương đương với điều này:

FileStream fs = File.OpenRead("C:\\temp\\myfile.txt")) 
try 
{ 
    // Read a text file 1024 bytes at a time and write it to the console 
    byte[] b = new byte[1024]; 
    while (fs.Read(b, 0, b.Length) > 0) 
    { 
     Console.WriteLine(Encoding.UTF8.GetString(b)); 
    } 
} 
finally 
{ 
    fs.Dispose(); 
} 
1

Các Pattern Vứt bỏ sẽ là tốt nhất để sử dụng. Đây là việc thực hiện đầy đủ công việc của bạn.

Hãy nhớ rằng bạn phải gọi Vứt bỏ theo cách của riêng bạn như được thực hiện trong mã bên dưới.

public static void Main() 
    { 
     Panda[] forest_panda = new Panda[10]; 
     for (int i = 0; i < forest_panda.GetLength(0); i++) 
      forest_panda[i] = new Panda("P1"); 

     // Dispose the pandas by your own 
     foreach (var panda in forest_panda) 
      panda.Dispose(); 


     for (int i = 0; i < forest_panda.GetLength(0); i++) 
      forest_panda[i] = new Panda("P1"); 

     // Dispose the pandas by your own 
     foreach (var panda in forest_panda) 
      panda.Dispose(); 

     Console.WriteLine("Total Pandas created is {0}", Panda.population); 
    } 

    class Panda : IDisposable 
    { 
     public static int population = 0; 
     public string name; 

     public Panda(string name) 
     { 
      this.name = name; 
      population = population + 1; 
     } 

     ~Panda() 
     { 
      Dispose(false); 
     } 

     /// <summary> 
     /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. 
     /// </summary> 
     /// <filterpriority>2</filterpriority> 
     public void Dispose() 
     { 
      Dispose(true); 
      GC.SuppressFinalize(this); 
     } 

     private void Dispose(bool disposing) 
     { 
      if (disposing) 
      { 
       population = population - 1; 
      } 
     } 
    } 
Các vấn đề liên quan