Ответ 1
Обновлен, хороший момент by @SriramSakthivel, оказалось, что я уже ответил на очень похожий вопрос:
Почему GC собирает мой объект, когда у меня есть ссылка на него?
Итак, я отмечаю это как вики сообщества.
Однако, пусть SetResult (..) никогда не вызывается и someClassInstance перестает ссылаться и собирается мусор. Это создает утечку памяти? Или .Net автоматически узнает контекст вызова должен быть удален?
Если по вызову-контексту вы имеете в виду объект автомата, созданный компилятором (который представляет состояние метода async
), то да, он действительно будет завершен.
Пример:
static void Main(string[] args)
{
var task = TestSomethingAsync();
Console.WriteLine("Press enter to GC");
Console.ReadLine();
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true);
GC.WaitForFullGCComplete();
GC.WaitForPendingFinalizers();
Console.WriteLine("Press enter to exit");
Console.ReadLine();
}
static async Task TestSomethingAsync()
{
using (var something = new SomeDisposable())
{
await something.WaitForThingAsync();
}
}
class SomeDisposable : IDisposable
{
readonly TaskCompletionSource<string> _tcs = new TaskCompletionSource<string>();
~SomeDisposable()
{
Console.WriteLine("~SomeDisposable");
}
public Task<string> WaitForThingAsync()
{
return _tcs.Task;
}
public void Dispose()
{
Console.WriteLine("SomeDisposable.Dispose");
GC.SuppressFinalize(this);
}
}
Вывод:
Press enter to GC ~SomeDisposable Press enter to exit
IMO, это поведение логично, но все же может быть немного неожиданным, что something
завершается, несмотря на то, что область using
для него никогда не заканчивалась (и, следовательно, ее SomeDisposable.Dispose
никогда не вызывалась) и что Task
, возвращаемый TestSomethingAsync
, все еще жив и указан в Main
.
Это может привести к некоторым неясным ошибкам при кодировании асинхронных данных на системном уровне. Очень важно использовать GCHandle.Alloc(callback)
для любых обратных вызовов взаимодействия с ОС, на которые не ссылаются внешние методы async
. Выполнение GC.KeepAlive(callback)
только в конце метода async
неэффективно. Я писал об этом подробнее:
Async/await, пользовательский awaiter и сборщик мусора
На боковой заметке существует еще один тип конечного автомата С#: метод с return yield
. Интересно, что наряду с IEnumerable
или IEnumerator
он также реализует IDisposable
. Вызов его Dispose
приведет к отключению любых операторов using
и finally
(даже в случае неполной перечислимой последовательности):
static IEnumerator SomethingEnumerable()
{
using (var disposable = new SomeDisposable())
{
try
{
Console.WriteLine("Step 1");
yield return null;
Console.WriteLine("Step 2");
yield return null;
Console.WriteLine("Step 3");
yield return null;
}
finally
{
Console.WriteLine("Finally");
}
}
}
// ...
var something = SomethingEnumerable();
something.MoveNext(); // prints "Step 1"
var disposable = (IDisposable)something;
disposable.Dispose(); // prints "Finally", "SomeDisposable.Dispose"
В отличие от этого, при методах async
нет прямого способа управления нераскрытием using
и finally
.