假设我有以下类(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 自动神奇地知道需要处理调用上下文?
最佳答案
已更新,@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, this behavior is logical, but it still might be a bit unexpected that something
gets finalized despite the fact that the using
scope for it has never ended (and hence its SomeDisposable.Dispose
has never been called) and that the Task
returned by TestSomethingAsync
is still alive and referenced in Main
.
This could lead to some obscure bugs when coding system-level asynchronous stuff. It's really important to use GCHandle.Alloc(callback)
on any OS interop callbacks which are not referenced outside async
methods. Doing GC.KeepAlive(callback)
alone at the end of the async
method is not effective. I wrote about this in details here:
Async/await, custom awaiter and garbage collector
On a side note, there's another kind of C# state machine: a method with return yield
. Interestingly, along with IEnumerable
or IEnumerator
, it also implements IDisposable
. Invoking its Dispose
will unwind any using
and finally
statements (even in the case of incomplete enumerable sequence):
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
的展开。
关于c# - 从未完成的任务会怎样?他们是否妥善处置?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28257095/