从未完成的任务会发生什么? 他们妥善处理?

说我有以下课程:

class SomeClass { private TaskCompletionSource _someTask; public Task WaitForThing() { _someTask = new TaskCompletionSource(); return _someTask.Task; } //Other code which calls _someTask.SetResult(..); } 

然后在别处,我打电话

 //Some code.. await someClassInstance.WaitForThing(); //Some more code 

//Some more code在调用_someTask.SetResult(..)之前,不会调用//Some more code 。 调用上下文在内存中等待。

但是,假设从未调用过SetResult(..) ,并且someClassInstance停止被引用并被垃圾收集。 这会造成内存泄漏吗? 或.Net自动神奇地知道需要处理调用上下文?

更新了@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 _tcs = new TaskCompletionSource(); ~SomeDisposable() { Console.WriteLine("~SomeDisposable"); } public Task WaitForThingAsync() { return _tcs.Task; } public void Dispose() { Console.WriteLine("SomeDisposable.Dispose"); GC.SuppressFinalize(this); } } 

输出:

按enter键进入GC

 〜SomeDisposable
按enter键退出

IMO,这种行为是合乎逻辑的,但是尽管事实上它的using范围从未结束(因此它的SomeDisposable.Dispose从未被调用)并且TestSomethingAsync返回的Task ,但事情仍然可能有点意外。仍然活着并在Main引用。

在编码系统级异步内容时,这可能会导致一些模糊的错误。 在任何未在async方法之外引用的OS互操作回调中使用GCHandle.Alloc(callback)非常重要。 在async方法结束时单独执行GC.KeepAlive(callback)无效。 我在这里详细描述了这个:

异步/等待,自定义awaiter和垃圾收集器

另外 ,还有另一种C#状态机:一种具有return yield的方法。 有趣的是,与IEnumerableIEnumerator ,它还实现了IDisposable 。 调用它的Dispose将展开任何usingfinally语句(即使在可枚举序列不完整的情况下):

 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方法没有直接的方法来控制usingfinally的解除。

您应确保始终完成任务 。

在通常情况下,“调用SetResult的其他代码”在某处注册为回调。 例如,如果它使用非托管重叠I / O,则该回调方法是GC根。 然后该回调显式地使_someTask保持活动状态,这使得其Task保持活动状态,从而使_someTask保持//Some more code存活。

如果“调用SetResult的其他代码” 没有 (直接或间接)注册为回调,那么我认为不会有泄漏。 请注意,这不是受支持的用例,因此无法保证。 但我确实使用你问题中的代码创建了一个内存分析测试,它似乎没有泄漏。