![](/img/trans.png)
[英]What happens to an await call that hasn't completed before the caller is disposed?
[英]What happens to Tasks that are never completed? Are they properly disposed?
說我有以下課程:
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(..)
之前,不會調用//Some more code
。 調用上下文在內存中等待。
但是,假設從不調用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);
}
}
輸出:
Press enter to GC ~SomeDisposable Press enter to exit
IMO,這種行為是合乎邏輯的,但它仍然可能是一個有點意外的是something
得到盡管最終確定using
范圍為它從來沒有結束(因此其SomeDisposable.Dispose
從未被調用),而且Task
返回的TestSomethingAsync
仍然活着並在Main
引用。
在編碼系統級異步內容時,這可能會導致一些模糊的錯誤。 在任何未在async
方法之外引用的OS互操作回調中使用GCHandle.Alloc(callback)
非常重要。 在async
方法結束時單獨執行GC.KeepAlive(callback)
無效。 我在這里詳細介紹了這個:
另一方面 ,還有另一種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
的解除。
在通常情況下,“調用SetResult的其他代碼”在某處注冊為回調。 例如,如果它使用非托管重疊I / O,則該回調方法是GC根。 然后該回調顯式地使_someTask
保持活動狀態,這使得其Task
保持活動狀態,從而使_someTask
保持//Some more code
存活。
如果“其他調用SetResult的代碼” 沒有 (直接或間接)注冊為回調,那么我認為不會有泄漏。 請注意,這不是受支持的用例,因此無法保證。 但我確實使用你問題中的代碼創建了一個內存分析測試,它似乎沒有泄漏。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.