我正在我的窗口中执行异步任务:
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 - > ...
如果我理解正确,如果/ FetchData
完成,继续应继续在窗口实例目标和线程上,但自从线程完成后就不会发生。
有没有解决方法,在这种情况下如何避免内存泄漏?
答案 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
所以你可以忍受(如果你认为这是一个小漏洞 - 因为用户真正打开了多少个窗口才能产生明显效果,成千上万?)或以某种方式跟踪你的待处理操作并阻止关闭窗口直到它们是全部已解决(或允许关闭窗口,但阻止调度程序关闭,直到所有待处理的操作都得到解决)。