简体   繁体   English

WPF框架在后台线程中使用Dispatcher.CurrentDispatcher,导致内存泄漏

[英]WPF framework using Dispatcher.CurrentDispatcher in background thread, causing memory leak

I'm using a WPF CollectionView and I'm setting the Filter in a background thread, because it takes a long time to apply this filter.我正在使用 WPF CollectionView 并且我正在后台线程中设置过滤器,因为应用此过滤器需要很长时间。

Setting this Filter triggers the method ScheduleMapCleanup () of CollectionView (so WPF framework code I can't change).设置这个过滤器会触发 CollectionView 的ScheduleMapCleanup () 方法(所以 WPF 框架代码我无法更改)。 In this method, Dispatcher.CurrentDispatcher.BeginInvoke is used.在此方法中,使用了 Dispatcher.CurrentDispatcher.BeginInvoke。

However, because this is executed in a background thread, this Action is never executed (the Dispatcher of this thread is never started), causing a memory leak: The Dispatcher keeps a reference to the CollectionView.但是,因为这是在后台线程中执行的,所以这个Action永远不会被执行(这个线程的Dispatcher永远不会启动),导致内存泄漏:Dispatcher保留了对CollectionView的引用。

How could I work around this problem?我该如何解决这个问题? Setting the Filter in the UI thread is not an option.在 UI 线程中设置过滤器不是一个选项。

Could I start the Dispatcher myself?我可以自己启动 Dispatcher 吗? If so, how do I do this (Dispatcher.Run halts everything)?如果是这样,我该怎么做(Dispatcher.Run 停止一切)?

I use this when I need to update some controls and binding on my UI thread from my background tasks:当我需要从后台任务更新 UI 线程上的一些控件和绑定时,我会使用它:

Application.Current.Dispatcher.Invoke(
    DispatcherPriority.Loaded,
    new Action(() => {

        // Code here

    })
);

If it's not this can you be more specific on what you want to do on your UI thread如果不是这样,你可以更具体地说明你想在你的 UI 线程上做什么

Accessing the current dispatcher from a background thread does not give you the UI dispatcher, it gives you a new one for the background thread.从后台线程访问当前调度程序不会为您提供 UI 调度程序,它为后台线程提供了一个新的调度程序。

Either call CurrentDispatcher from the foreground thread and pass the result to the background thread, or call DependencyObject.Dispatcher to get the dispatcher for a window or other control.从前台线程调用CurrentDispatcher并将结果传递给后台线程,或者调用DependencyObject.Dispatcher以获取窗口或其他控件的调度程序。


Edit: I just read the question more closely.编辑:我只是更仔细地阅读了这个问题。 Since you do not control the code calling CurrentDispatcher , the only way that it will work is to call that code from the UI thread.由于您不控制调用CurrentDispatcher的代码,因此它起作用的唯一方法是从 UI 线程调用该代码。

To be clear: I don't use Dispatcher.CurrentDispatcher in my code.需要明确的是:我没有在我的代码中使用 Dispatcher.CurrentDispatcher。 This is used in the WPF framework code, so I can't change this.这是在 WPF 框架代码中使用的,因此我无法更改它。 This code is executed in a background thread because I'm setting the Filter in a background thread.这段代码在后台线程中执行,因为我在后台线程中设置了过滤器。 I'm setting this property in a background thread because it can take up to several minutes.我在后台线程中设置此属性,因为它可能需要几分钟。 Setting it in a background thread keeps the UI responsive and lets me show a loading indication to the user.在后台线程中设置它可以保持 UI 响应并让我向用户显示加载指示。

I fixed the memory leak (caused by the not-running background Dispatcher keeping a reference to the CollectionView) by adding a Shutdown to the Dispatcher and starting the dispatcher in the background thread:我通过向 Dispatcher 添加 Shutdown 并在后台线程中启动调度程序来修复内存泄漏(由未运行的后台 Dispatcher 保持对 CollectionView 的引用引起):

//All code below is executed on a background thread

//Line below causes WPF framework to add something to Dispatcher.CurrentDispatcher queue.
view.Filter = new Predicate<Object>(actionTarget.FilterCallback); 

if (Thread.CurrentThread.IsBackground &&  Dispatcher.CurrentDispatcher != Application.Current.Dispatcher)
{
    Dispatcher.CurrentDispatcher.BeginInvokeShutdown(DispatcherPriority.Background);
    Dispatcher.Run();
}

If the background thread is reused later (for example because it's a thread pool thread, started by a BackgroundWorker) you can't use BeginInvokeShutdown like in the code above: a shut down dispatcher can not be started again.如果后台线程稍后被重用(例如,因为它是一个由 BackgroundWorker 启动的线程池线程),您不能像上面的代码一样使用 BeginInvokeShutdown:无法再次启动关闭的调度程序。 In that case, use this instead of the BeginInvokeShutdown:在这种情况下,请使用它而不是 BeginInvokeShutdown:

Dispatcher.CurrentDispatcher.BeginInvoke((Action) delegate() { Dispatcher.ExitAllFrames(); }, DispatcherPriority.Background);

This will make sure the Run() method returns, but the dispatcher can be started again later on.这将确保 Run() 方法返回,但稍后可以再次启动调度程序。

Edit: As Mitch mentioned in comment below, be carefull when multiple threads can be executing the Run() at the same time.编辑:正如米奇在下面的评论中提到的,当多个线程可以同时执行 Run() 时要小心。 If necessary add a lock around the Run().如有必要,请在 Run() 周围添加一个锁。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM