Aktualisiert, ein guter Punkt von @SriramSakthivel, stellt sich heraus, ich habe bereits eine sehr ähnliche Frage beantwortet:
Why does GC collects my object when I have a reference to it?
So bin ich dieses als Community Wiki markiert.
Allerdings sagen wir mal SetResult (..) nie aufgerufen wird, und someClassInstance nicht mehr Bezug genommen wird, und ist Müll gesammelt. Erstellt dies ein Speicherleck? Oder kennt .Net automatisch den Aufruf-Kontext, der entsorgt werden muss?
Wenn durch den rufenden Kontext Sie das Compiler-generierte Zustandsmaschine Objekt bedeutet (die den Zustand der async
Methode darstellt), dann ja, es wird in der Tat abgeschlossen sein.
Beispiel:
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);
}
}
Ausgang:
Press enter to GC
~SomeDisposable
Press enter to exit
IMO, dieses Verhalten ist logisch, aber es könnte noch ein wenig unerwartet, dass something
trotz der Tatsache fertig gestellt wird, dass die using
Bereich für es ist nie zu Ende (und daher seine SomeDisposable.Dispose
wurde nie genannt) und dass die Task
zurückgegeben von TestSomethingAsync
ist noch am Leben und referenziert in Main
.
Dies könnte zu einigen obskuren Fehlern bei der Codierung von asynchronen Dateien auf Systemebene führen. Es ist sehr wichtig, GCHandle.Alloc(callback)
für alle OS-Interop-Callbacks zu verwenden, die nicht außerhalb der async
Methoden referenziert werden. Doing allein am Ende der async
Methode ist nicht effektiv. Ich schrieb darüber in Details hier:
Async/await, custom awaiter and garbage collector
Auf einer seitlichen Anmerkung, gibt es eine andere Art von C# Zustandsmaschine: ein Verfahren mit return yield
. Interessanterweise implementiert es zusammen mit IEnumerable
oder IEnumerator
auch IDisposable
. Unter Berufung auf seine Dispose
wird entspannen alle using
und finally
Aussagen (auch im Fall einer unvollständigen enumerable Sequenz):
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"
Im Gegensatz zu diesem, mit async
Methoden gibt es keine direkte Möglichkeit, das unwiding von using
und finally
steuern.
Ein TCS ist ein isoliertes Objekt. Wenn es nicht erreichbar ist, wird es gesammelt. Seine Task verweist nicht auf das TCS, sodass Sie eine nicht kompilierbare Task ohne ein entsprechendes TCS im Speicher haben können. Wenn diese Aufgabe nicht referenziert wird, wird sie ebenfalls gesammelt. – usr