任务等待和线程终止导致内存泄漏

时间:2018-04-02 08:40:10

标签: c# wpf multithreading async-await

我正在我的窗口中执行异步任务:

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完成之前发生这种情况,我会发现内存泄漏。

似乎永远等待FetchDataTask.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 sample

如果我理解正确,如果/ FetchData完成,继续应继续在窗口实例目标和线程上,但自从线程完成后就不会发生。

有没有解决方法,在这种情况下如何避免内存泄漏?

1 个答案:

答案 0 :(得分:7)

我认为你无法解决这个问题(我的意思是不改变整体设计)。通过等待和可用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的{​​{1}}。当然,传递给DispatcherOperationStatus.Aborted的委托在这种情况下不会被执行。

所以在结果中 - 你的BeginInvoke的继续被传递给shutdown dispatcher,它默默地忽略它(好吧,不是默默地 - 它返回Aborted状态,但是没有办法观察它,因为LoadData返回类型为SynchronizationContext.Post)。由于永远不会执行延续 - 从void返回的任务永远不会完成\ failed \ cancels并且始终处于运行状态。

您可以通过提供自己的同步上下文来验证它,看看它是如何进行的:

LoadData

所以你可以忍受(如果你认为这是一个小漏洞 - 因为用户真正打开了多少个窗口才能产生明显效果,成千上万?)或以某种方式跟踪你的待处理操作并阻止关闭窗口直到它们是全部已解决(或允许关闭窗口,但阻止调度程序关闭,直到所有待处理的操作都得到解决)。