简体   繁体   中英

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.

Setting this Filter triggers the method ScheduleMapCleanup () of CollectionView (so WPF framework code I can't change). In this method, Dispatcher.CurrentDispatcher.BeginInvoke is used.

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.

How could I work around this problem? Setting the Filter in the UI thread is not an option.

Could I start the Dispatcher myself? If so, how do I do this (Dispatcher.Run halts everything)?

I use this when I need to update some controls and binding on my UI thread from my background tasks:

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

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.

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.


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.

To be clear: I don't use Dispatcher.CurrentDispatcher in my code. This is used in the WPF framework code, so I can't change this. 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.

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:

//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. In that case, use this instead of the 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.

Edit: As Mitch mentioned in comment below, be carefull when multiple threads can be executing the Run() at the same time. If necessary add a lock around the Run().

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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