Посмотрим на следующий фрагмент, который показывает проблему.
class Program
{
static void Main(string[] args)
{
var task = Start();
Task.Run(() =>
{
Thread.Sleep(500);
Console.WriteLine("Starting GC");
GC.Collect();
GC.WaitForPendingFinalizers();
Console.WriteLine("GC Done");
});
task.Wait();
Console.Read();
}
private static async Task Start()
{
Console.WriteLine("Start");
Synchronizer sync = new Synchronizer();
var task = sync.SynchronizeAsync();
await task;
GC.KeepAlive(sync);//Keep alive or any method call doesn't help
sync.Dispose();//I need it here, But GC eats it :(
}
}
public class Synchronizer : IDisposable
{
private TaskCompletionSource<object> tcs;
public Synchronizer()
{
tcs = new TaskCompletionSource<object>(this);
}
~Synchronizer()
{
Console.WriteLine("~Synchronizer");
}
public void Dispose()
{
Console.WriteLine("Dispose");
}
public Task SynchronizeAsync()
{
return tcs.Task;
}
}
Выход:
Start
Starting GC
~Synchronizer
GC Done
Как вы можете видеть, sync
получает Gc'd (более конкретно финализированный, мы не знаем, что память исправлена или нет). Но почему? Почему GC собирает мой объект, когда у меня есть ссылка на него?
Исследование:
Я потратил некоторое время на изучение того, что происходит за кулисами. Похоже, что конечная машина, сгенерированная компилятором С#, хранится как локальная переменная, а после первого удара await
кажется, что сам конечный автомат выходит из сферы действия,
Таким образом, GC.KeepAlive(sync);
и sync.Dispose();
не помогают, поскольку они живут внутри конечного автомата, где в качестве конечного автомата не существует.
Компилятор С# не должен генерировать код, из-за которого мой экземпляр sync
выходит из области видимости, когда мне это все еще нужно. Это ошибка в компиляторе С#? Или я пропущу что-то фундаментальное?
PS: Я не ищу обходного пути, а скорее объяснение, почему компилятор делает это? Я googled, но не нашел каких-либо связанных вопросов, если это дублирует извините за это.
Update1: Я изменил создание TaskCompletionSource
для хранения экземпляра Synchronizer
, который по-прежнему не помогает.