2015-03-19 16 views
8

Tôi theo dõi một đối tượng sử dụng WeakReference<T> (short weak reference) trong lớp học của tôi Foo. Lớp này có một destructor mà tôi cần truy cập đối tượng được theo dõi đó. Đối tượng tôi theo dõi cũng đang theo dõi Foo bằng cách sử dụng WeakReference<Foo>.Khi nào các tham chiếu ngắn yếu trở thành null?

Vì vậy, bây giờ tôi tự hỏi, khi nào chính xác "zeroing" của WeakReference xảy ra? Làm tất cả WeakReference được nulled trước khi bất kỳ finalizer được chạy, hoặc làm mỗi người trong số họ có được nulled ngay trước khi finalizer của đối tượng họ theo dõi là về để chạy?

CẬP NHẬT

Bây giờ tôi cũng tự hỏi, nếu có thể Mono dự án có thể làm sáng tỏ về cái này (link 1, link 2). Nhưng tôi hơi lo lắng rằng có thể MS GCMono GC có thể tiếp cận vấn đề này một cách khác nhau và là không tương thích.

+0

Wow, đó là tài liệu khó hiểu ... nó * trông * giống như tài liệu tham chiếu yếu ngắn nên nói về quyết toán, chứ không phải thu gom rác - nhưng không rõ. –

+0

Tôi đã xem xét mã nguồn SSCLI cũ để thử xem những gì tôi có thể tìm thấy.Đã không tìm thấy bất cứ điều gì rõ ràng nhưng đã tìm thấy một bình luận hấp dẫn bên trong 'Mục tiêu 'getter:" Chỉ nên xảy ra khi sử dụng bất hợp pháp, như sử dụng một WeakReference từ finalizer "- vì vậy có lẽ những gì bạn đang cố gắng làm là cam chịu anyway. –

+1

Không phải là câu trả lời nhưng đáng lưu ý rằng bạn nên tránh phá hủy và điều này rất có thể là vấn đề XY. –

Trả lời

4

Bạn có thể tự xác minh bằng chương trình thử nghiệm đơn giản. Nhưng tôi tìm thấy các tài liệu cho các loại WeakReference chính nó để được phần nào rõ ràng hơn so với trang bạn đang xem xét.

Cụ thể, cờ được gọi là "ngắn" và "dài" trong trang được liên kết của bạn được gọi là trackResurrection trong the actual constructor documentation. Và mô tả cho thông số lần đọc:

Cho biết thời điểm dừng theo dõi đối tượng. Nếu đúng, đối tượng được theo dõi sau khi hoàn thành; nếu sai, đối tượng chỉ được theo dõi cho đến khi hoàn thành.

Các "Ghi chú" phần còn đọc:

Nếu trackResurrection là sai, một tham chiếu yếu ngắn được tạo ra. Nếu trackResurrection là true, một tham chiếu yếu dài được tạo ra.

Điều này khẳng định rằng khi bạn sử dụng một "ngắn" tài liệu tham khảo yếu, một đối tượng hoàn thành sẽ không còn được theo dõi (tức là Target trở thành null) bởi đối tượng WeakReference, nhưng khi bạn sử dụng một "dài" tham khảo yếu, nó sẽ là.

Đối với cả hai loại tham chiếu yếu, một đối tượng thực sự đã được thu gom rác sẽ chắc chắn không còn được theo dõi nữa (hiển nhiên). Nói chung, không có chủ đề nào khác trong chương trình của bạn có thể quan sát đối tượng trong khi chuỗi kết thúc thực sự đang thực hiện công việc của nó, vì vậy thời điểm chính xác cho tham chiếu yếu "ngắn" khi thuộc tính Target được đặt thành null dường như tranh luận với tôi. Nếu một số luồng khác trong chương trình của bạn quan sát giá trị là không null, trình hoàn thiện chưa được chạy. Nếu nó quan sát nó là null, finalizer đã chạy. Đó là "thread khác" không nên tự chạy trong khi thread finalizer đang làm việc, do đó, cuối cùng về cơ bản nên được nguyên tử như xa như rằng "thread khác" là có liên quan.

5

Tôi đã nghĩ viết một chương trình demo nhỏ thể hiện sự khác biệt. Hóa ra hơi khó khăn hơn tôi đếm. Thành phần cần thiết đầu tiên là để đảm bảo rằng chuỗi finalizer có thể được làm chậm lại để bạn có thể quan sát giá trị của WeakReference.IsAlive mà không có nguy cơ bị ảnh hưởng bởi một cuộc đua với thread finalizer.Vì vậy, tôi đã sử dụng:

class FinalizerDelayer { 
    ~FinalizerDelayer() { 
     Console.WriteLine("Delaying finalizer..."); 
     System.Threading.Thread.Sleep(500); 
     Console.WriteLine("Delay done"); 
    } 
} 

Sau đó một chút lớp đó sẽ là mục tiêu của WeakReference:

class Example { 
    private int instance; 
    public Example(int instance) { this.instance = instance; } 
    ~Example() { 
     Console.WriteLine("Example {0} finalized", instance); 
    } 
} 

Sau đó, một chương trình thể hiện sự khác biệt giữa một dài và một tham chiếu yếu ngắn:

class Program { 
    static void Main(string[] args) { 
     var target1 = new Example(1); 
     var target2 = new Example(2); 
     var shortweak = new WeakReference(target1); 
     var longweak = new WeakReference(target2, true); 
     var delay = new FinalizerDelayer(); 
     GC.Collect();  // Kills short reference 
     Console.WriteLine("Short alive = {0}", shortweak.IsAlive); 
     Console.WriteLine("Long alive = {0}", longweak.IsAlive); 
     GC.WaitForPendingFinalizers(); 
     Console.WriteLine("Finalization done"); 
     GC.Collect();  // Kills long reference 
     Console.WriteLine("Long alive = {0}", longweak.IsAlive); 
     Console.ReadLine(); 
    } 
} 

Bạn phải chạy chương trình này để trình gỡ lỗi không thể ảnh hưởng đến thời gian sống của các đối tượng. Chọn bản phát hành Bản phát hành và thay đổi cài đặt trình gỡ lỗi: Công cụ + Tùy chọn, Gỡ lỗi, Chung, bỏ chọn tùy chọn "Loại bỏ tối ưu hóa JIT".

Đã tắt thứ tự hoàn thành của các đối tượng thực sự là không xác định. Thứ tự là khác nhau mỗi khi bạn chạy chương trình. Chúng tôi muốn đối tượng FinalizerDelayer được hoàn thành trước nhưng điều đó không phải lúc nào cũng xảy ra. Tôi nghĩ rằng là một tác dụng phụ đối với tính năng Random Space Layout được tích hợp sẵn, nó làm cho mã được quản lý rất khó tấn công. Nhưng chạy nó thường đủ và cuối cùng bạn sẽ nhận được:

Trì hoãn finalizer ...
ngắn sống = False
dài sống = True
trễ thực hiện
Ví dụ 1 hoàn
Ví dụ 2 hoàn
Quyết toán thực hiện
dài sống = False

câu chuyện dài ngắn:

  • Một ngắn bộ tài liệu tham khảo yếu IsAlive để sai ngay khi đối tượng được thu thập và được đặt trên hàng đợi freachable, sẵn sàng cho nó được hoàn thành. Đối tượng vẫn tồn tại về mặt vật lý nhưng không có tham chiếu mạnh nào tồn tại nữa, nó sẽ sớm được hoàn thành.
  • Một tham chiếu dài yếu theo dõi đối tượng suốt quãng thời gian thực của cuộc đời, kể cả cuộc sống của nó trên hàng đợi có thể xử lý được. IsAlive sẽ không được đặt thành false cho đến khi trình hoàn tất của nó hoàn tất.

Cẩn thận với một quirk khi đối tượng được phục hồi, di chuyển trở lại từ hàng đợi có thể xử lý sang vùng thông thường khi tham chiếu mạnh được tạo lại. Không phải cái gì tôi đã khám phá trong chương trình demo này, nhưng cần phải có một tham chiếu dài yếu để quan sát điều đó. Lý do cơ bản tại sao bạn cần một tham chiếu dài yếu.

0

Vì vậy, sau khi nghiên cứu nhiều, tôi đã có thể tìm thấy bài viết cổ này Garbage Collection Part 2: Automatic Memory Management in the Microsoft .NET Framework (part 1 thảo luận về sự sống lại và đợi freachable):

Bây giờ, đây là những gì sẽ xảy ra khi một bộ sưu tập rác (GC) chạy:

  1. Bộ thu gom rác tạo biểu đồ của tất cả các đối tượng có thể truy cập. Phần 1 của bài viết này đã thảo luận cách thức hoạt động của nó.

  2. Bộ thu gom rác quét bảng tham chiếu yếu ngắn. Nếu một con trỏ trong bảng đề cập đến một đối tượng không phải là một phần của biểu đồ, thì con trỏ xác định đối tượng không thể truy cập và vị trí trong bảng tham chiếu yếu ngắn được đặt thành null.

  3. Bộ thu gom rác quét hàng đợi cuối cùng. Nếu một con trỏ trong hàng đợi đề cập đến một đối tượng không phải là một phần của đồ thị, thì con trỏ xác định một đối tượng không thể truy cập và con trỏ được di chuyển từ hàng đợi cuối cùng đến hàng đợi có thể xử lý được. Tại thời điểm này, đối tượng được thêm vào biểu đồ vì đối tượng hiện được coi là có thể truy cập.

  4. Bộ thu gom rác quét bảng tham chiếu dài yếu. Nếu một con trỏ trong bảng đề cập đến một đối tượng không phải là một phần của đồ thị (mà bây giờ chứa các đối tượng được trỏ tới bởi các mục trong hàng đợi có thể phân tán), thì con trỏ xác định một đối tượng không thể truy cập và vị trí được đặt là rỗng.

  5. Bộ thu gom rác sẽ thu gọn bộ nhớ, nén các lỗ còn sót lại bởi các đối tượng không thể truy cập.

Vì vậy, mặc dù lớp học của tôi Foo có một finalizer và do đó nó sẽ ở trong một hàng đợi freachable (được coi là một root) - sự giải quyết tài liệu tham khảo yếu ngắn xảy ra TRƯỚC đối tượng này được bắt nguồn từ hàng đợi có thể xử lý được, có nghĩa là tham chiếu yếu ngắn sẽ là rỗng:

Bộ thu gom rác đặt con trỏ thành null trong bảng tham chiếu yếu ngắn ngay khi xác định đối tượng không thể truy cập được. Nếu đối tượng có phương thức Finalize, phương thức này chưa được gọi để đối tượng vẫn tồn tại. Nếu ứng dụng truy cập thuộc tính Target của đối tượng WeakReference, thì null sẽ được trả về mặc dù đối tượng thực sự vẫn tồn tại.


Hơn nữa, như đã nói ở trên Yun Jin's WebLog, nói chung là không tốt để tham khảo đối tượng finalizable trong finalizers, nhưng WeakReference là một chút của một ngoại lệ (mặc dù this was not always the case). Vì WeakReference là một đối tượng với finalizer, nếu nó được truy cập trong finalizer của Foo, nó có thể đã được hoàn thành (trong trường hợp này, thuộc tính Target sẽ luôn trả về null, mặc dù thực tế là đối tượng được theo dõi có thể vẫn còn sống và tốt (more info) .


tôi vừa xác nhận rằng Mono garbage collector là phù hợp với hành vi này


tài liệu tham khảo hữu ích:.
- WeakReference mã nguồn
- WeakReference<T> mã nguồn

+0

_ "Vì WeakReference là một đối tượng với finalizer" _ - bạn vẫn chắc chắn rằng bạn đang đi đúng hướng ở đây? –

+0

@ HenkHolterman Nó làm phức tạp mọi thứ bởi rất nhiều, nhưng hy vọng tôi có thể làm việc xung quanh bằng cách nào đó. Chẳng hạn như bằng cách thừa kế từ một WeakReference và đưa tất cả những logic ưa thích mà tôi cần vào đó. :-) Thứ tự của những gì GC làm là hoàn toàn quan trọng để làm cho nó đúng. – Paya

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