WeakReference ทำงานแตกต่างกันระหว่าง. Net Framework และ. Net Core

Aug 19 2020

พิจารณารหัสต่อไปนี้:

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] };
        }
    }
}
  • ด้วยรีลีสหรือดีบักบิลด์บน. Net Framework 4.8 โค้ดนั้นจะพิมพ์Falseออกมา
  • ด้วยรีลีสหรือดีบักบิลด์บน. Net Core 3.1 โค้ดนั้นจะพิมพ์Trueออกมา

อะไรทำให้เกิดความแตกต่างในพฤติกรรมนี้? (ทำให้การทดสอบหน่วยของเราล้มเหลว)

หมายเหตุ: ฉันใส่การเริ่มต้นรายการmakeList()และปิดการใช้งานอินไลน์ในความพยายามที่จะทำให้เวอร์ชัน. Net Core ทำงานเหมือนกับเวอร์ชัน. Net Framework แต่ไม่มีประโยชน์


[แก้ไข] ดังที่ฮันส์ชี้ให้เห็นการเพิ่มลูปจะช่วยแก้ไขได้

รหัสต่อไปนี้จะพิมพ์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);

แต่สิ่งนี้จะพิมพ์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);

นี้ได้มีจะเป็นชนิดของสิ่งอัดฉีดบางลาง ...

คำตอบ

3 Servy Aug 19 2020 at 18:14

เพียงเพราะบางสิ่งบางอย่างได้รับอนุญาตให้รวบรวมไม่ได้หมายความว่าจำเป็นต้องรวบรวมโดยเร็วที่สุด ในขณะที่ภาษาระบุว่า GC ได้รับอนุญาตให้ตรวจสอบว่าตัวแปรโลคัลจะไม่ถูกอ่านอีกดังนั้นจึงไม่ถือว่าเป็นรูทนั่นไม่ได้หมายความว่าคุณสามารถพึ่งพาเนื้อหาของตัวแปรในเครื่องที่รวบรวมได้ทันทีหลังจากที่คุณอ่านครั้งสุดท้าย .

นี่ไม่ใช่การเปลี่ยนแปลงบางอย่างระหว่างพฤติกรรมที่กำหนดไว้ในรันไทม์นี่คือพฤติกรรมที่ไม่ได้กำหนดในช่วงเวลาทั้งสองดังนั้นความแตกต่างระหว่างพฤติกรรมเหล่านี้จึงยอมรับได้

AndreasSynnerdahl Sep 03 2020 at 01:09

ฉันได้รับการอ้างอิงที่จะเป็นอิสระเมื่อฉันลบตัวแปรรายการ:

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

กรณีทดสอบต่อไปนี้ล้มเหลว:

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

หากเราดู IL เราสามารถเห็นว่าโมฆะถูกกำหนดให้กับตัวแปรโลคัล 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