簡體   English   中英

任務等待和線程終止導致內存泄漏

[英]Task await and thread terminate are causing memory leak

我正在我的窗口中執行異步任務:

private async Task LoadData()
{
    // Fetch data
    var data = await FetchData();

    // Display data
    // ...
}

該窗口在單獨的線程中啟動:

// Create and run new thread
var thread = new Thread(ThreadMain);
thread.SetApartmentState(ApartmentState.STA);
thread.IsBackground = true;
thread.CurrentCulture = Thread.CurrentThread.CurrentCulture;
thread.CurrentUICulture = Thread.CurrentThread.CurrentUICulture;
thread.Start();

private static void ThreadMain()
{
    // Set synchronization context
    var dispatcher = Dispatcher.CurrentDispatcher;
    SynchronizationContext.SetSynchronizationContext(
        new DispatcherSynchronizationContext(dispatcher));

    // Show window
    var window = new MyWindow();
    window.ShowDialog();

    // Shutdown
    dispatcher.InvokeShutdown();
}

當窗戶關閉時,線程當然已經完成,這很好。

現在 - 如果在FetchData完成之前發生這種情況,我會得到內存泄漏。

看來, FetchData是永存的期待和Task.s_currentActiveTasks靜態字段永遠保持我的窗口實例:

保留路徑

System.Collections.Generic.Dictionary.entries - > System.Collections.Generic.Dictionary + Entry [37] at [17] .value - > System.Threading.Tasks.Task.m_continuationObject - > System.Threading.Tasks.SynchronizationContextAwaitTaskContinuation。 m_action - > System.Action._target - > System.Runtime.CompilerServices.AsyncMethodBuilderCore + ContinuationWrapper.m_continuation - > System.Action._target - > System.Runtime.CompilerServices.AsyncMethodBuilderCore + ContinuationWrapper.m_continuation - > System.Action._target - > ...

dotMemory示例

如果我理解正確,如果/當FetchData完成時,繼續應繼續在窗口實例目標和線程上,但自從線程完成后就不會發生。

有沒有解決方法,在這種情況下如何避免內存泄漏?

我認為你無法解決這個問題(我的意思是不改變整體設計)。 通過等待和可用的SynchronizationContext ,將繼續( await之后)發布到該上下文。 此延續包括完成結果任務的代碼。 所以在你的例子中:

private async Task LoadData()
{
    var data = await FetchData();
    // the rest is posted to sync context
    // and only when the rest is finished
    // task returned from LoadData goes to completed state         
} 

發布到WPF同步上下文與在調度程序上執行BeginInvoke相同。 但是,這樣做BeginInvoke上調度具有關機沒有拋出異常,返回DispatcherOperation與狀態DispatcherOperationStatus.Aborted 當然,在這種情況下,不會執行傳遞給BeginInvoke

所以在結果中 - 你的LoadData繼續被傳遞給shutdown dispatcher,它默默地忽略它(好吧,不是默默地 - 它返回Aborted狀態,但是沒有辦法觀察它,因為SynchronizationContext.Post返回類型為void )。 由於永遠不會執行延續 - 從LoadData返回的任務永遠不會完成\\ failed \\ cancels並且始終處於運行狀態。

您可以通過提供自己的同步上下文來驗證它,看看它是如何進行的:

internal class SimpleDispatcherContext : SynchronizationContext
{
    private readonly Dispatcher _dispatcher;
    private readonly DispatcherPriority _priority;
    public SimpleDispatcherContext(Dispatcher dispatcher, DispatcherPriority priority = DispatcherPriority.Normal)
    {
        _dispatcher = dispatcher;
        _priority = priority;
    }

    public override void Post(SendOrPostCallback d, object state) {
        var op = this._dispatcher.BeginInvoke(_priority, d, state);
        // here, default sync context just does nothing
        if (op.Status == DispatcherOperationStatus.Aborted)
            throw new OperationCanceledException("Dispatcher has shut down");
    }

    public override void Send(SendOrPostCallback d, object state) {
        _dispatcher.Invoke(d, _priority, state);
    }
}

private async Task LoadData()
{
    SynchronizationContext.SetSynchronizationContext(new SimpleDispatcherContext(Dispatcher));
    // Fetch data
    var data = await FetchData();

    // Display data
    // ...
}

所以你可以忍受這種情況(如果你認為這是一個小漏洞 - 因為用戶真正打開多少個窗口才能產生明顯的效果,成千上萬?)或以某種方式跟蹤你的待處理操作並防止關閉窗口直到它們全部解決(或允許關閉窗口但阻止調度程序關閉,直到所有待處理的操作都得到解決)。

暫無
暫無

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

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