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, но безрезультатно.


[EDIT] Как указал Ганс, добавление цикла исправляет это.

Будет напечатан следующий код 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

Просто потому , что что - то позволено быть собраны не означает , что она обязана быть собраны , как только возможно. Хотя в языке указано, что сборщику мусора разрешено определять, что локальная переменная больше не читается, и, следовательно, не считает ее корнем, это не означает, что вы можете полагаться на то, что содержимое локальной переменной собирается сразу после того, как вы в последний раз читали из нее. .

Это не какое-то изменение между определенным поведением во время выполнения, это неопределенное поведение в обеих средах выполнения, поэтому различия между ними вполне приемлемы.

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, мы увидим, что значение null присваивается локальной переменной 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