繁体   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