WeakReference berperilaku berbeda antara .Net Framework dan .Net Core

Aug 19 2020

Perhatikan kode berikut:

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] };
        }
    }
}
  • Dengan rilis atau versi debug pada .Net Framework 4.8, kode itu dicetak False.
  • Dengan rilis atau versi debug pada .Net Core 3.1, kode itu dicetak True.

Apa yang menyebabkan perbedaan perilaku ini? (Ini menyebabkan beberapa pengujian unit kami gagal.)

Catatan: Saya memasukkan inisialisasi daftar ke makeList()dan mematikan sebaris dalam upaya untuk membuat versi .Net Core berfungsi sama dengan versi .Net Framework, tetapi tidak berhasil.


[EDIT] Seperti yang ditunjukkan Hans, menambahkan loop akan memperbaikinya.

Kode berikut akan mencetak 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);

Tapi ini akan mencetak 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);

Ini telah mendapat menjadi semacam hal Jitter aneh ...

Jawaban

3 Servy Aug 19 2020 at 18:14

Hanya karena sesuatu diperbolehkan untuk dikumpulkan, bukan berarti itu wajib dikumpulkan secepatnya. Meskipun bahasa menyatakan bahwa GC diizinkan untuk menentukan bahwa variabel lokal tidak akan pernah dibaca lagi, dan dengan demikian tidak menganggapnya sebagai root, itu tidak berarti Anda dapat mengandalkan konten variabel lokal yang dikumpulkan segera setelah Anda terakhir membacanya. .

Ini bukanlah perubahan antara perilaku yang ditentukan dalam waktu proses, ini adalah perilaku yang tidak ditentukan di kedua waktu proses, jadi perbedaan di antara keduanya sepenuhnya dapat diterima.

AndreasSynnerdahl Sep 03 2020 at 01:09

Saya mendapat referensi untuk dibebaskan, ketika saya menghapus variabel daftar:

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);
        }
   }
}

Kasus uji berikut gagal:

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);
        }
   }
}

Jika kita melihat IL kita dapat melihat bahwa null ditugaskan ke variabel lokal 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