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

Dus*_*san 6 c# wpf multithreading async-await

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

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

    // Display data
    // ...
}
Run Code Online (Sandbox Code Playgroud)

该窗口在单独的线程中启动:

// 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();
}
Run Code Online (Sandbox Code Playgroud)

当窗户关闭时,线程当然已经完成,这很好.

现在 - 如果在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完成时,继续应继续在窗口实例目标和线程上,但自从线程完成后就不会发生.

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

Evk*_*Evk 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         
} 
Run Code Online (Sandbox Code Playgroud)

发布到WPF同步上下文与BeginInvoke在调度程序上执行相同.但是,BeginInvoke对已关闭的调度程序执行操作会抛出没有异常并返回DispatcherOperation状态为DispatcherOperationStatus.Aborted.当然,BeginInvoke在这种情况下,您传递给的委托不会被执行.

所以在结果中 - 你的继续LoadData传递给shutdown调度程序,它默默地忽略它(好吧,不是默默地 - 它返回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
    // ...
}
Run Code Online (Sandbox Code Playgroud)

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