Async-Await Error with Local Variable Clearing

I ran into a problem when it seems that local resources cannot be cleaned up during garbage collection if the resources are in an asynchronous wait method.

I created a code sample to illustrate the problem.

Simpleclass

SimpleClass uses a static counter to record the number of active instances by increasing the _count static field during construction and decreasing the same field during destruction.

using System;

namespace AsyncGarbageCollector
{
    public class SimpleClass
    {

        private static readonly object CountLock = new object();
        private static int _count;

        public SimpleClass()
        {
            Console.WriteLine("Constructor is called");
            lock (CountLock)
            {
                _count++;
            }
        }

        ~SimpleClass()
        {
            Console.WriteLine("Destructor is called");
            lock (CountLock)
            {
                _count--;
            }
        }

        public static int Count
        {
            get
            {
                lock (CountLock)
                {
                    return _count;
                }
            }
        }
    }
}

Program

Here is the main program that has three tests

  • The standard call that initializes the class and then the variable is left unchanged.
  • An asynchronous call that initializes the class and then the variable is left out of scope
  • , , null ,

GC.Collect. , .

using System;
using System.Threading.Tasks;

namespace AsyncGarbageCollector
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Press 1, 2 or 3 to start.\n\n");
            var code = Console.ReadKey(true);

            if (code.Key == ConsoleKey.D1)
                RunTest1();
            else if (code.Key == ConsoleKey.D2)
                RunTest2Async().Wait();
            else if (code.Key == ConsoleKey.D3)
                RunTest3Async().Wait();


            Console.WriteLine("\n\nPress any key to close.");
            Console.ReadKey();
        }

        private static void RunTest1()
        {
            Console.WriteLine("Test 1\n======");
            TestCreation();
            DisplayCounts();
        }

        private static async Task RunTest2Async()
        {
            Console.WriteLine("Test 2\n======");
            await TestCreationAsync();
            DisplayCounts();
        }

        private static async Task RunTest3Async()
        {
            Console.WriteLine("Test 3\n======");
            await TestCreationNullAsync();
            DisplayCounts();
        }

        private static void TestCreation()
        {
            var simple = new SimpleClass();
        }

        private static async Task TestCreationAsync()
        {
            var simple = new SimpleClass();
            await Task.Delay(50);
        }

        private static async Task TestCreationNullAsync()
        {
            var simple = new SimpleClass();
            await Task.Delay(50);

            Console.WriteLine("Setting Null");
            simple = null;
        }

        private static void DisplayCounts()
        {
            Console.WriteLine("Running GC.Collect()");
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();

            Console.WriteLine("Count: " + SimpleClass.Count);
        }
    }
}

Test 1
======
Constructor is called
Running GC.Collect()
Destructor is called
Count: 0
Returned to Main
Running GC.Collect()
Count: 0

Test 2
======
Constructor is called
Running GC.Collect()
Count: 1
Returned to Main
Running GC.Collect()
Destructor is called
Count: 0

Test 3
======
Constructor is called
Setting Null
Running GC.Collect()
Destructor is called
Count: 0
Returned to Main
Running GC.Collect()
Count: 0

2 SimpleClass ( ), .

? , "", , , , .

. ?

  • , .
  • , , - async-.

/ .

+4
2

async/await . :

private static async Task RunTest2Async()
{
    Console.WriteLine("Test 2\n======");
    await TestCreationAsync();
    DisplayCounts();
}

- . TestCreationAsync() Task. . , .

, , TestCreationAsync(), , RunTest2Async() ( ). , . , , simple - . .

Release, simple await. , , . .

:

Visualization

+7

Async - .

,

void async MyMethod()
{
    int k = await Some1();
    await Some2();
}

()

struct MyMethodState
{
    int k;
    int stage;
    Task currentTaskToWaitFor;
}

( , async)

- Ildasm, .

, .

+3

Source: https://habr.com/ru/post/1542487/


All Articles