说我有以下课程:
class SomeClass
{
private TaskCompletionSource<string> _someTask;
public Task<string> WaitForThing()
{
_someTask = new TaskCompletionSource<string>();
return _someTask.Task;
}
//Other code which calls _someTask.SetResult(..);
}
然后在其他地方,我打电话给
//Some code..
await someClassInstance.WaitForThing();
//Some more code
在调用//Some more code
之前,不会调用_someTask.SetResult(..)
。调用上下文在内存中等待。
但是,假设永远不会调用SetResult(..)
,并且someClassInstance
停止被引用并被垃圾收集。这会造成内存泄漏吗?或.Net自动神奇地知道需要处理调用上下文?
答案 0 :(得分:9)
更新了,@ SriramSakthivel提出了一个很好的观点,事实证明我已经回答了一个非常类似的问题:
Why does GC collects my object when I have a reference to it?
所以我将此标记为社区维基。
然而,让我们说SetResult(..)永远不会被调用,而且 someClassInstance停止被引用并被垃圾收集。 这会造成内存泄漏吗?或者.Net自动神奇地知道了 呼叫上下文需要处理吗?
如果通过调用上下文表示编译器生成的状态机对象(表示async
方法的状态),那么是的,它确实将被最终确定。< / p>
示例:强>
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)
方法之外未引用的任何OS互操作回调上使用async
非常重要。在return yield
方法结束时单独执行IEnumerable
无效。我在这里详细描述了这个:
Async/await, custom awaiter and garbage collector
旁注,还有另一种C#状态机: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
方法无法直接控制{{1}}和{{1}}的解除。
答案 1 :(得分:6)
You should ensure your tasks are always completed
在通常情况下,&#34;其他代码调用SetResult&#34;在某处注册为回调。例如,如果它使用非托管重叠I / O,则该回调方法是GC根。然后该回调明确地使_someTask
保持活动状态,这使其Task
保持活动状态,从而使//Some more code
的委托保持活动状态。
如果&#34;其他代码调用SetResult&#34;是不(直接或间接)注册为回调,然后我认为会有泄漏。请注意,这不是受支持的用例,因此无法保证。但我确实使用你问题中的代码创建了一个内存分析测试,它似乎没有泄漏。