更新,@SriramSakthivel 的一个好观点,原来我已经回答了一个非常相似的问题:
Why does GC collects my object when I have a reference to it?
所以我将此标记为社区 wiki。
但是,假设 SetResult(..) 从未被调用,并且
someClassInstance 停止被引用并被垃圾收集。
这会造成内存泄漏吗?或者.Net是否自动神奇地知道
call-context 需要被释放吗?
如果 calling-context 是指编译器生成的状态机对象(代表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);
}
}
输出:
按回车键进入 GC
〜一些一次性
按回车退出
IMO,这种行为是合乎逻辑的,但尽管using 的作用域从未结束(因此它的SomeDisposable.Dispose 从未被调用过),但something 最终确定仍然可能有点意外并且 TestSomethingAsync 返回的 Task 仍然存在并且在 Main 中引用。
在编写系统级异步代码时,这可能会导致一些难以理解的错误。在async 方法之外未引用的任何操作系统互操作回调上使用GCHandle.Alloc(callback) 非常重要。在async方法的最后单独做GC.KeepAlive(callback)是无效的。我在这里详细写了这个:
Async/await, custom awaiter and garbage collector
附带说明,还有另一种 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 的展开。