从未完成的任务会怎样?他们是否妥善处置?
What happens to Tasks that are never completed? Are they properly disposed?
假设我有以下 class:
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
在调用 _someTask.SetResult(..)
之前不会调用 //Some more code
。调用上下文在内存中某处等待。
但是,假设 SetResult(..)
从未被调用,并且 someClassInstance
停止被引用并被垃圾收集。这会造成内存泄漏吗?还是 .Net 自动神奇地知道需要处理调用上下文?
You should ensure your tasks are always completed.
在通常情况下,"Other code which calls SetResult" 在某处注册为回调。例如,如果它使用非托管重叠 I/O,那么该回调方法就是 GC 根。然后该回调显式地保持 _someTask
活动,从而使其 Task
保持活动状态,从而使 //Some more code
的委托保持活动状态。
如果"Other code which calls SetResult"没有(直接或间接)注册为回调,那么我认为会有泄漏。请注意,这不是受支持的用例,因此无法保证。但我确实使用你问题中的代码创建了一个内存分析测试,它似乎没有泄漏。
已更新,@SriramSakthivel 的一个好观点,事实证明我已经回答了一个非常相似的问题:
Why does GC collects my object when I have a reference to it?
所以我将此标记为社区 Wiki。
However, let's say SetResult(..) is never called, and
someClassInstance stops being referenced and is garbage collected.
Does this create a memory leak? Or does .Net auto-magically know the
calling-context needs to be disposed?
如果 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);
}
}
输出:
Press enter to GC
~SomeDisposable
Press enter to exit
IMO,这种行为是合乎逻辑的,但尽管 using
的范围从未结束(因此它的 SomeDisposable.Dispose
从未被调用过)并且 TestSomethingAsync
返回的 Task
仍然存在并在 Main
.
中被引用
这可能会导致在编写系统级异步内容时出现一些不明显的错误。在 async
方法之外未引用的任何 OS 互操作回调上使用 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
.
的展开
假设我有以下 class:
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
在调用 _someTask.SetResult(..)
之前不会调用 //Some more code
。调用上下文在内存中某处等待。
但是,假设 SetResult(..)
从未被调用,并且 someClassInstance
停止被引用并被垃圾收集。这会造成内存泄漏吗?还是 .Net 自动神奇地知道需要处理调用上下文?
You should ensure your tasks are always completed.
在通常情况下,"Other code which calls SetResult" 在某处注册为回调。例如,如果它使用非托管重叠 I/O,那么该回调方法就是 GC 根。然后该回调显式地保持 _someTask
活动,从而使其 Task
保持活动状态,从而使 //Some more code
的委托保持活动状态。
如果"Other code which calls SetResult"没有(直接或间接)注册为回调,那么我认为会有泄漏。请注意,这不是受支持的用例,因此无法保证。但我确实使用你问题中的代码创建了一个内存分析测试,它似乎没有泄漏。
已更新,@SriramSakthivel 的一个好观点,事实证明我已经回答了一个非常相似的问题:
Why does GC collects my object when I have a reference to it?
所以我将此标记为社区 Wiki。
However, let's say SetResult(..) is never called, and someClassInstance stops being referenced and is garbage collected. Does this create a memory leak? Or does .Net auto-magically know the calling-context needs to be disposed?
如果 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);
}
}
输出:
Press enter to GC ~SomeDisposable Press enter to exit
IMO,这种行为是合乎逻辑的,但尽管 using
的范围从未结束(因此它的 SomeDisposable.Dispose
从未被调用过)并且 TestSomethingAsync
返回的 Task
仍然存在并在 Main
.
这可能会导致在编写系统级异步内容时出现一些不明显的错误。在 async
方法之外未引用的任何 OS 互操作回调上使用 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
.