从未完成的任务会发生什么? 他们妥善处理?
说我有以下课程:
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
的方法。 有趣的是,与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
的解除。
您应确保始终完成任务 。
在通常情况下,“调用SetResult的其他代码”在某处注册为回调。 例如,如果它使用非托管重叠I / O,则该回调方法是GC根。 然后该回调显式地使_someTask
保持活动状态,这使得其Task
保持活动状态,从而使_someTask
保持//Some more code
存活。
如果“调用SetResult的其他代码” 没有 (直接或间接)注册为回调,那么我认为不会有泄漏。 请注意,这不是受支持的用例,因此无法保证。 但我确实使用你问题中的代码创建了一个内存分析测试,它似乎没有泄漏。