WeakReference hoạt động khác nhau giữa .Net Framework và .Net Core

Aug 19 2020

Hãy xem xét đoạn mã sau:

using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;

#nullable enable

namespace ConsoleApp1
{
    class Program
    {
        static void Main()
        {
            var list    = makeList();
            var weakRef = new WeakReference(list[0]);

            list[0] = null;
            GC.Collect();
            GC.WaitForPendingFinalizers();

            Console.WriteLine(weakRef.IsAlive);
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        static List<int[]?> makeList()
        {
            return new List<int[]?> { new int[2] };
        }
    }
}
  • Với bản phát hành hoặc bản dựng gỡ lỗi trên .Net Framework 4.8, mã đó sẽ in False.
  • Với bản phát hành hoặc bản dựng gỡ lỗi trên .Net Core 3.1, mã đó sẽ in True.

Điều gì đang gây ra sự khác biệt trong hành vi này? (Nó khiến một số bài kiểm tra đơn vị của chúng tôi không thành công.)

Lưu ý: Tôi đã đặt khởi tạo danh sách vào makeList()và tắt nội tuyến nhằm cố gắng làm cho phiên bản .Net Core hoạt động giống như phiên bản .Net Framework, nhưng vô ích.


[EDIT] Như Hans đã chỉ ra, việc thêm một vòng lặp sẽ khắc phục sự cố.

Đoạn mã sau sẽ in False:

var list    = makeList();
var weakRef = new WeakReference(list[0]);

list[0] = null;

for (int i = 0; i < 1; ++i)
    GC.Collect();

Console.WriteLine(weakRef.IsAlive);

Nhưng điều này sẽ in True:

var list    = makeList();
var weakRef = new WeakReference(list[0]);

list[0] = null;

GC.Collect();
GC.Collect();
GC.Collect();
GC.Collect();

// Doesn't seem to matter how many GC.Collect() calls you do.

Console.WriteLine(weakRef.IsAlive);

Điều này đã được một số loại điều Jitter lạ ...

Trả lời

3 Servy Aug 19 2020 at 18:14

Chỉ vì thứ gì đó được phép thu thập không có nghĩa là nó bắt buộc phải được thu thập càng sớm càng tốt. Mặc dù ngôn ngữ nói rằng GC được phép xác định rằng một biến cục bộ không bao giờ được đọc lại và do đó không coi nó là gốc, điều đó không có nghĩa là bạn có thể dựa vào nội dung của một biến cục bộ được thu thập ngay sau khi bạn đọc lần cuối từ nó .

Đây không phải là một số thay đổi giữa hành vi xác định trong thời gian chạy, đây là hành vi không xác định trong cả hai thời gian chạy, vì vậy sự khác biệt giữa chúng là hoàn toàn có thể chấp nhận được.

AndreasSynnerdahl Sep 03 2020 at 01:09

Tôi đã giải phóng tham chiếu khi tôi xóa biến danh sách:

using NUnit.Framework;
using System;
using System.Collections.Generic;

namespace NUnitTestProject1
{
    public class Tests
    {
        [TestCase(2, GCCollectionMode.Forced, true)]
        public void TestWeakReferenceWithList(int generation, GCCollectionMode forced, bool blocking)
        {
            static WeakReference CreateWeakReference()
            {
                return new WeakReference(new List<int[]> { new int[2] });
            }

            var x = CreateWeakReference();

            Assert.IsTrue(x.IsAlive);

            GC.Collect(generation, forced, blocking);

            Assert.IsFalse(x.IsAlive);
        }
   }
}

Trường hợp kiểm tra sau không thành công:

using NUnit.Framework;
using System;
using System.Collections.Generic;

namespace NUnitTestProject1
{
    public class Tests
    {
        [TestCase(2, GCCollectionMode.Forced, true)]
        public void TestWeakReferenceWithList(int generation, GCCollectionMode forced, bool blocking)
        {
            static List<int[]> CreateList()
            {
                return new List<int[]> { new int[2] };
            }

            WeakReference x;

            {
                var list = CreateList();

                x = new WeakReference(list);

                list = null;
            }
            
            Assert.IsTrue(x.IsAlive);

            GC.Collect(generation, forced, blocking);

            Assert.IsFalse(x.IsAlive);
        }
   }
}

Nếu chúng ta nhìn vào IL, chúng ta có thể thấy rằng null được gán cho biến cục bộ 1:

IL_0003:  call       class [System.Collections]System.Collections.Generic.List`1<int32[]> NUnitTestProject1.Tests::'<TestWeakReferenceWithList>g__CreateList|0_0'()
IL_0008:  stloc.1
IL_0009:  ldloc.1
IL_000a:  newobj     instance void [System.Runtime]System.WeakReference::.ctor(object)
IL_000f:  stloc.0
IL_0010:  ldnull
IL_0011:  stloc.1
IL_0012:  nop