簡體   English   中英

從未完成的任務會發生什么? 他們妥善處理?

[英]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的一個好點,結果我已經回答了一個非常類似的問題:

為什么GC在我引用它時會收集我的對象?

所以我將這個標記為社區維基。

但是,假設從不調用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)無效。 我在這里詳細介紹了這個:

異步/等待,自定義awaiter和垃圾收集器

另一方面 ,還有另一種C#狀態機:一種具有return yield的方法。 有趣的是,與IEnumerableIEnumerator ,它還實現了IDisposable 調用它的Dispose將展開任何usingfinally語句(即使在可枚舉序列不完整的情況下):

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方法沒有直接的方法來控制usingfinally的解除。

您應確保始終完成任務

在通常情況下,“調用SetResult的其他代碼”在某處注冊為回調。 例如,如果它使用非托管重疊I / O,則該回調方法是GC根。 然后該回調顯式地使_someTask保持活動狀態,這使得其Task保持活動狀態,從而使_someTask保持//Some more code存活。

如果“其他調用SetResult的代碼” 沒有 (直接或間接)注冊為回調,那么我認為不會有泄漏。 請注意,這不是受支持的用例,因此無法保證。 但我確實使用你問題中的代碼創建了一個內存分析測試,它似乎沒有泄漏。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM