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 버전과 동일하게 작동하지만 아무 소용이 없도록하기 위해 목록 초기화를 넣고 인라인을 해제했습니다.


[편집] Hans가 지적했듯이 루프를 추가하면 문제가 해결됩니다.

다음 코드 인쇄됩니다 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을 보면 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