2013-03-04 35 views
11

Tôi có một bài kiểm tra đó tôi mong đợi để vượt qua nhưng hành vi của các Collector rác không phải là tôi coi:Garbage Collection nên đã gỡ bỏ đối tượng nhưng WeakReference.IsAlive vẫn trở về đúng

[Test] 
public void WeakReferenceTest2() 
{ 
    var obj = new object(); 
    var wRef = new WeakReference(obj); 

    wRef.IsAlive.Should().BeTrue(); //passes 

    GC.Collect(); 

    wRef.IsAlive.Should().BeTrue(); //passes 

    obj = null; 

    GC.Collect(); 

    wRef.IsAlive.Should().BeFalse(); //fails 
} 

Trong ví dụ này obj đối tượng nên được GC'd và do đó tôi mong đợi tài sản WeakReference.IsAlive trả lại false.

Dường như vì biến số obj được khai báo trong cùng phạm vi với GC.Collect, nó không được thu thập. Nếu tôi di chuyển các khai báo obj và khởi tạo bên ngoài của phương pháp kiểm tra vượt qua.

Có ai có tài liệu tham khảo kỹ thuật hoặc giải thích về hành vi này không?

+1

Bạn đã kiểm tra xem mã IL trông như thế nào? Ngoài ra, nó hoạt động theo cùng một cách để phát hành và gỡ lỗi xây dựng? –

+3

Đoán ban đầu của tôi là tối ưu hóa trình biên dịch/thời gian chạy/bộ xử lý đang cắn bạn. Họ nhận ra bạn không bao giờ đọc 'obj' vì vậy nó được phép sắp xếp lại các hoạt động giữa các cuộc gọi phương thức khác. Hãy thử thêm một cái gì đó như 'Console.WriteLine (obj == null)' chỉ để ngăn chặn trình biên dịch làm điều đó. – Servy

+1

Mẫu này hoạt động tốt trên máy của tôi. Tôi đang sử dụng 'Console.WriteLine' để ghi tham số' IsAlive' mặc dù thay vì 'Should()' – JaredPar

Trả lời

6

Lượt cùng một vấn đề với bạn - thử nghiệm của tôi đã được chuyển đi khắp mọi nơi, ngoại trừ dưới NCrunch (có thể là bất kỳ thiết bị đo đạc nào khác trong trường hợp của bạn). Hm. Gỡ lỗi với SOS tiết lộ thêm rễ được tổ chức trên một cuộc gọi ngăn xếp của một phương pháp thử nghiệm. Đoán của tôi là chúng là kết quả của các công cụ mã hóa mà vô hiệu hoá bất kỳ tối ưu hóa trình biên dịch nào, bao gồm cả những công cụ tính toán khả năng tiếp cận đối tượng một cách chính xác.

Cách chữa trị ở đây khá đơn giản - không bao giờ giữ tài liệu tham khảo mạnh mẽ từ phương pháp thực hiện GC và thử nghiệm cho sự sống động. Điều này có thể dễ dàng đạt được với một phương pháp trợ giúp tầm thường. Sự thay đổi bên dưới đã khiến trường hợp kiểm tra của bạn vượt qua với NCrunch, nơi ban đầu nó không thành công.

[TestMethod] 
public void WeakReferenceTest2() 
{ 
    var wRef2 = CallInItsOwnScope(() => 
    { 
     var obj = new object(); 
     var wRef = new WeakReference(obj); 

     wRef.IsAlive.Should().BeTrue(); //passes 

     GC.Collect(); 

     wRef.IsAlive.Should().BeTrue(); //passes 
     return wRef; 
    }); 

    GC.Collect(); 

    wRef2.IsAlive.Should().BeFalse(); //used to fail, now passes 
} 

private T CallInItsOwnScope<T>(Func<T> getter) 
{ 
    return getter(); 
} 
+0

Cảm ơn vì điều này! Một giải pháp rất thanh lịch. Tôi đã sử dụng NCrunch quá. – TechnoTone

+0

Giải pháp này làm việc cho tôi mặc dù tôi đã đơn giản hóa nó một chút. Tôi đặt các hoạt động "CallInItsOwnScope" trong một chức năng riêng biệt hơn là trong một lambda và điều này cho phép GC buộc phải thực hiện như mong đợi. – Kent

2

Có thể là phương pháp mở rộng .Should() bằng cách nào đó đang treo vào tham chiếu? Hoặc có lẽ một số khía cạnh khác của khung kiểm tra đang gây ra vấn đề này.

(Tôi gửi bài này như một câu trả lời nếu không tôi không thể dễ dàng gửi mã!)

Tôi đã thử đoạn code sau, và nó hoạt động như mong đợi (Visual Studio 2012, Net 4 xây dựng, gỡ lỗi và phát hành, 32 bit và 64 bit, chạy trên Windows 7, bộ xử lý lõi tứ):

using System; 

namespace Demo 
{ 
    internal class Program 
    { 
     private static void Main(string[] args) 
     { 
      var obj = new object(); 
      var wRef = new WeakReference(obj); 

      GC.Collect(); 
      obj = null; 
      GC.Collect(); 

      Console.WriteLine(wRef.IsAlive); // Prints false. 
      Console.ReadKey(); 
     } 
    } 
} 

Điều gì sẽ xảy ra khi bạn thử mã này?

+2

Nếu 'nên' đang nắm giữ một tham chiếu, nó sẽ cần phải được trong một biến tĩnh, nếu không nó sẽ đi ra khỏi phạm vi và được thu gom rác thải. – Servy

+0

Đồng ý và điều đó dường như không xảy ra. Nhưng mà không nhìn thấy mã nguồn cho nó, tôi phải thừa nhận khả năng đó. Mặc dù tôi nghĩ thực sự nó chỉ là "hành vi không xác định" đối với thời gian khi các tham chiếu vô hiệu hóa thực sự trở thành rác thu thập được, như bạn đã đề xuất. –

+0

"Nên" là từ thư viện FluentAssertions. Tôi nhận được cùng một hành vi bằng cách sử dụng Assert.False thay thế. – TechnoTone

4

Theo như tôi biết, hãy gọi Collect không đảm bảo đảm bảo rằng tất cả các tài nguyên đều được phát hành. Bạn chỉ đơn thuần là đưa ra một gợi ý cho người thu gom rác.

Bạn có thể cố gắng ép buộc nó để chặn cho đến khi tất cả các đối tượng được phát hành bằng cách làm này:

GC.Collect(2, GCCollectionMode.Forced, true); 

Tôi hy vọng điều này có thể không hoạt động hoàn toàn 100% thời gian. Nói chung, tôi sẽ tránh viết bất kỳ mã nào phụ thuộc vào việc quan sát bộ thu gom rác, nó không thực sự được thiết kế để sử dụng theo cách này.

9

Có một vài vấn đề tiềm năng tôi có thể thấy:

  • Tôi không biết bất cứ điều gì trong thư mục C# đặc điểm kỹ thuật mà đòi hỏi rằng kiếp sống của các biến địa phương bị hạn chế. Trong một bản dựng không gỡ lỗi, tôi nghĩ trình biên dịch sẽ được tự do bỏ nhiệm vụ cuối cùng cho obj (thiết lập nó thành null) vì không có đường dẫn mã nào gây ra giá trị obj sẽ không bao giờ được sử dụng sau nó, nhưng tôi mong đợi điều đó một bản sửa lỗi không gỡ lỗi sẽ cho biết biến không bao giờ được sử dụng sau khi tạo tham chiếu yếu. Trong bản dựng gỡ lỗi, biến phải tồn tại trong phạm vi chức năng, nhưng câu lệnh obj = null; thực sự nên xóa nó. Tuy nhiên, tôi không chắc chắn rằng C# spec hứa hẹn rằng trình biên dịch sẽ không bỏ qua câu lệnh cuối cùng mà vẫn giữ biến xung quanh.

  • Nếu bạn đang sử dụng bộ thu gom rác đồng thời, có thể là GC.Collect() kích hoạt khởi động ngay lập tức của bộ sưu tập, nhưng bộ sưu tập sẽ không thực sự được hoàn thành trước khi trả về GC.Collect().Trong trường hợp này, có thể không cần thiết phải chờ cho tất cả các finalizers chạy, và do đó GC.WaitForPendingFinalizers() có thể quá mức cần thiết, nhưng có lẽ nó sẽ giải quyết được vấn đề.

  • Khi sử dụng bộ thu gom rác tiêu chuẩn, tôi không mong đợi sự tồn tại của một tham chiếu yếu đến đối tượng để kéo dài sự tồn tại của đối tượng theo cách mà bộ hoàn thiện sẽ sử dụng. các đối tượng bị bỏ rơi mà một tham chiếu yếu tồn tại được chuyển đến hàng đợi các đối tượng với các tham chiếu yếu cần được dọn dẹp và việc xử lý dọn dẹp đó xảy ra trên một luồng riêng biệt chạy đồng thời với mọi thứ khác. Trong trường hợp này, một cuộc gọi đến GC.WaitForPendingFinalizers() sẽ là cần thiết để đạt được hành vi mong muốn.

Lưu ý rằng ta nên thường không hy vọng rằng tài liệu tham khảo yếu sẽ không còn giá trị với bất kỳ mức độ cụ thể của tính kịp thời, và cũng không nên hy vọng rằng lấy Target sau IsAlive báo cáo đúng sẽ mang lại một tham chiếu không null. Bạn chỉ nên sử dụng IsAlive trong trường hợp người ta không quan tâm đến mục tiêu nếu nó vẫn còn sống, nhưng sẽ muốn biết rằng tham chiếu đã chết. Ví dụ: nếu một bộ sưu tập gồm WeakReference đối tượng, người ta có thể muốn lặp lại định kỳ thông qua danh sách và xóa WeakReference đối tượng có mục tiêu đã chết. Người ta nên chuẩn bị cho khả năng rằng WeakReferences có thể vẫn còn trong bộ sưu tập lâu hơn là lý tưởng cần thiết; kết quả duy nhất nếu họ làm như vậy nên có một sự lãng phí nhỏ của bộ nhớ và thời gian CPU.

0

Tôi có cảm giác rằng bạn cần gọi số GC.WaitForPendingFinalizers() vì tôi cho rằng tài liệu tham khảo tuần được cập nhật bởi chuỗi kết thúc.

Tôi đã gặp vấn đề với nhiều năm trước khi viết một bài kiểm tra đơn vị và nhớ lại rằng WaitForPendingFinalizers() đã giúp, do đó đã thực hiện cuộc gọi đến GC.Collect().

Phần mềm không bao giờ bị rò rỉ trong cuộc sống thực, nhưng viết một bài kiểm tra đơn vị để chứng minh rằng đối tượng không được giữ sống còn khó hơn rất nhiều sau đó tôi hy vọng. (Chúng tôi đã có lỗi trong quá khứ với bộ nhớ cache của chúng tôi đã giữ nó sống.)

Các vấn đề liên quan