Blu*_*eft 15 c# task-parallel-library async-await
说我有以下课程:
class SomeClass
{
private TaskCompletionSource<string> _someTask;
public Task<string> WaitForThing()
{
_someTask = new TaskCompletionSource<string>();
return _someTask.Task;
}
//Other code which calls _someTask.SetResult(..);
}
Run Code Online (Sandbox Code Playgroud)
然后别的,我打电话
//Some code..
await someClassInstance.WaitForThing();
//Some more code
Run Code Online (Sandbox Code Playgroud)
在//Some more code被调用之前不会被_someTask.SetResult(..)调用.调用上下文在内存中等待.
但是,假设SetResult(..)从未被调用过,并且someClassInstance停止被引用并被垃圾收集.这会造成内存泄漏吗?或.Net自动神奇地知道需要处理调用上下文?
更新了@SriramSakthivel的一个好点,事实证明我已经回答了一个非常类似的问题:
所以我将这个标记为社区维基.
但是,假设从不调用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<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);
}
}
Run Code Online (Sandbox Code Playgroud)
输出:
Press enter to GC ~SomeDisposable Press enter to exit
IMO,这种行为是合乎逻辑的,但它仍然可能有点出乎意料,something尽管它的using范围从未结束(因此它SomeDisposable.Dispose从未被调用过),并且Task返回的TestSomethingAsync仍然存在且被引用Main.
在编码系统级异步内容时,这可能会导致一些模糊的错误.GCHandle.Alloc(callback)在任何未引用外部async方法的OS互操作回调中使用非常重要.这样做GC.KeepAlive(callback)在年底单独async方法是无效的.我在这里详细介绍了这个:
另外,还有另一种C#状态机:一种方法return yield.有趣的是,与IEnumerableor一起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"
Run Code Online (Sandbox Code Playgroud)
与此不同,用async的方法有控制的unwiding的没有直接的方法using和finally.
在通常情况下,"调用SetResult的其他代码"在某处注册为回调.例如,如果它使用非托管重叠I/O,则该回调方法是GC根.然后,该回调显式保持_someTask活动状态,使其保持Task活动状态,从而使委托保持//Some more code活动状态.
如果"其他调用SetResult的代码" 没有(直接或间接)注册为回调,那么我认为不会有泄漏.请注意,这不是受支持的用例,因此无法保证.但我确实使用你问题中的代码创建了一个内存分析测试,它似乎没有泄露.