惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
Threatpost
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
T
The Blog of Author Tim Ferriss
S
SegmentFault 最新的问题
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 司徒正美
T
Tailwind CSS Blog
The Cloudflare Blog
The Last Watchdog
The Last Watchdog
PCI Perspectives
PCI Perspectives
博客园 - 聂微东
Stack Overflow Blog
Stack Overflow Blog
TaoSecurity Blog
TaoSecurity Blog
云风的 BLOG
云风的 BLOG
C
Cybersecurity and Infrastructure Security Agency CISA
O
OpenAI News
Recorded Future
Recorded Future
GbyAI
GbyAI
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Y
Y Combinator Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
量子位
博客园 - 叶小钗
V
Vulnerabilities – Threatpost
F
Full Disclosure
Recent Announcements
Recent Announcements
Vercel News
Vercel News
S
Schneier on Security
H
Heimdal Security Blog
Cisco Talos Blog
Cisco Talos Blog
V2EX - 技术
V2EX - 技术
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
B
Blog RSS Feed
宝玉的分享
宝玉的分享
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
P
Privacy & Cybersecurity Law Blog
T
Threat Research - Cisco Blogs
G
Google Developers Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
爱范儿
爱范儿
IT之家
IT之家
大猫的无限游戏
大猫的无限游戏
C
Check Point Blog
N
Netflix TechBlog - Medium
S
Security @ Cisco Blogs
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Microsoft Azure Blog
Microsoft Azure Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Cyberwarzone
Cyberwarzone

博客园 - Anders06

我也谈面试 附赠一份题目 败给了IEqualityComparer 标注随测量物体旋转问题 How to detect memory leak issue 图纸中角度标注算法 认真的程序员最可爱 Clean Code反案例:什么是不好的API 避免陷阱,重写Equals方法您需要注意的其中2个原则 What’s the problems with the following code 对象池 关于设计和设计文档的2个补充 SelectMany ConverAll & Exists SOS:利用WPF RichTextBox 提取RTF, 莫名丢掉了Underline样式信息 想探讨两个关于设计文档问题 Don‘t Cry for Me, Argentina 拒绝高姿态 Please avoid implement C# Destruction SOS: How to popup a HwndSource on topmost
A memory leak issue with WPF Command Binding
Anders06 · 2013-10-29 · via 博客园 - Anders06

Background

In our application, we have a screen which hosts several tabs. In each tab, it contains a third-party  GridControl (like the WPF standard GridView control). And we need to display some column cells as hyper link, so user can click on it, view more details. So we have customized the Cell template as the following:

<DataTemplate x:Key="CellHyperlinkTemplate" >  
     <Button  IsHitTestVisible="True" VerticalAlignment="Center" Content="{Binding Value}" Command="{x:Static gridView:BaseView.GridClickCommand}" CommandParameter="{Binding RelativeSource={RelativeSource Self}}" ClickMode="Press">  
         <Button.Template>  
             <ControlTemplate TargetType="{x:Type Button}">  
                 <GridLayout Style="{DynamicResource defaultCellContentStyle}" Background="Transparent">  
                     < GridLayout.ColumnDefinitions>  
                         <ColumnDefinition Width="Auto"/>  
                     </GridLayout.ColumnDefinitions>  
                     <TextBlock Name="text" Foreground="#FF2A6DBD" Text="{TemplateBinding Content}" VerticalAlignment="Center" TextDecorations="Underline"/>  
                 </GridLayout>  
                 <ControlTemplate.Triggers>  
                     <Trigger Property="IsMouseOver" Value="true">  
                         <Setter Property="Foreground" Value="#FF5E9AE2" TargetName="text"/>  
                         <Setter Property="Cursor" Value="Hand" />  
                     </Trigger>  
                 </ControlTemplate.Triggers>  
             </ControlTemplate>  
         </Button.Template>  
      </Button>  
</DataTemplate>  

It is straightforward. Only one place need to note is we have binding the Button::Command to a static command.

At the beginning, the GridClickCommand is defining as a RoutedUICommand.

public class BaseView : DevExpress.Xpf.Grid.GridControl  
{  
    public static RoutedUICommand GridClickCommand = new RoutedUICommand();  
  
    public BaseView() : base()  
    {  
        CommandBindings.Add(new CommandBinding(GridClickCommand, GridClickCommandExecute));  
    } 

This is fine, until we have migrated the third party, and find the application is slow down after open the screens several times. The more times it opened the worse of the performance.

After struggled several weeks, we located the root problem is we misused the RoutedUICommand. We still have no idea why the problem didn’t happen before migrate the third party. But we can explain why the whole application performance slows down after open the screen.

Reason for the Performance Issue:

As I mentioned above, there are several hyper link cells in each tab, which binding to the RoutedUICommand. Let’s look down how RoutedCommand (the base class of RoutedUICommand) implements the CanExecuteChanged method

public event EventHandler CanExecuteChanged  
{  
    add { CommandManager.RequerySuggested += value;}  
    remove { CommandManager.RequerySuggested -= value;}  
}  

The purpose of above implementation is obviously, the command binding source (aka Button in this case) need update its state, which depends on the Command’s CanExecute method. When the Command’s CanExecute method returns True, the button displays in Enable state, otherwise in Disable state. The problem is who will notice the binding source that the CanExecute value has been changed. One possible solution is by ourselves, when we know the CanExecute value has been changed, we explicitly raise the CanExecuteChanged event, so the button updates its state accordingly.

However the disadvantage of above approach is obviously, you need to raise the CanExecuteChanged event in every place that will affect the CanExecute value. So the common approach in WPF is, we delegate it to CommandManager, that the reason why we register the event handler to CommandManager.RequerySuggested.

With the CommandManager, WPF can automatically ask all of the commands being used in your UI if they can execute. This happens at various times, such as when input focus shifts to another control, an item is selected in a list, etc. Refer more detail about Command and CommandManager from here.

Let’s come back to the reason why RoutedUICommand slows down the performance.  It’s simple, because it is a RoutedCommand, which need to route through the element tree to determine whether it should return True or False for its CanExecute method. So it’s very expensive. That’s the reason why the more the screen opened, the worse performance it is. Because, as the number of bindinged RoutedUICommand increase, the CommandManager takes more time to execute its update status logic. And even with your simple click, the logic could be execute, so it affects the whole application’s performance.

Solution for the Performance Issue:

In our scenario, there is totally no need to update status for button; it should be always kept in enable state. Not sure why we use the RoutedUICommand before. The fix is simple; we replace it with customized DelegateCommand, with the following implement.

public class DelegateCommand : ICommand  
{  
     private readonly Predicate<object> _canExecute;  
     private readonly Action<object> _execute;  
  
  
     public event EventHandler CanExecuteChanged;  
  
     public DelegateCommand(Action<object> execute): this(execute, null) { }  
  
  
     public DelegateCommand(Action<object> execute, Predicate<object> canExecute)  
     {  
         _execute = execute;  
         _canExecute = canExecute;  
     }  
  
  
     public bool CanExecute(object parameter)  
     {  
         if (_canExecute == null)          
            return true;  
         return _canExecute(parameter);  
     }  
  
  
     public void Execute(object parameter) { _execute(parameter); }  
  
  
     public void RaiseCanExecuteChanged()  
     {  
         if (CanExecuteChanged != null){  
             CanExecuteChanged(this, EventArgs.Empty);  
     }  
}  

Memory Leak Issue

Several days later, we notice there is a memory leak issue during doing the release check out. By leverage WinDbg, we could see the leak is related to above change. The GridClickCommand  command is static, and its field  CanExecuteChanged property keeps reference to event handlers, and indirectly reference to the binding source (the button), which caused the whole virtual tree of the screen could not be disposed.

Memory leak.bmp

Solution for the Memory Leak Issue

After google, I found this useful ticket, and resolved the problem with the following solution.

internal class NoReferenceDelegateCommand : ICommand  
{  
    protected readonly Action<object> _execute;  
  
  
    public NoReferenceDelegateCommand(Action<object> execute) { _execute = execute; }  
    public bool CanExecute(object parameter) { return true; }  
    public event EventHandler CanExecuteChanged  {  add { }  remove { } }  
public void Execute(object parameter) { _execute(parameter); }}  

The key point is implementing a dumb CanExecuteChanged, to avoid the binding source hock on this event. In our scenario, it is fine, since the command is always executable, no need to hock the CanExecuteChanged event, no need to raise this event.

More Explanation

Firstly, let’s check why the DelegateCommand causes the memory leak. As some guys said, there is no secret behind the source code. We could get the answer from the .Net Framework source code. For the button, in order to update its state correctly, it needs to hock up the command’s CanExecuteChanged event, and waiting for the event raised. So when you assign a command to button in XAML, it will automatically hock it for us.

static ButtonBase()
{  
       ….  
       ButtonBase.CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(ButtonBase), new FrameworkPropertyMetadata(null, new PropertyChangedCallback(ButtonBase.OnCommandChanged)));  
       ….  
}  
  
  
private static void OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{  
    ButtonBase buttonBase = (ButtonBase)d;  
    buttonBase.OnCommandChanged((ICommand)e.OldValue, (ICommand)e.NewValue);  
}  
private void OnCommandChanged(ICommand oldCommand, ICommand newCommand)
{
if (oldCommand != null) this.UnhookCommand(oldCommand); if (newCommand != null) this.HookCommand(newCommand); } private void HookCommand(ICommand command) { EventHandler value = new EventHandler(this.OnCanExecuteChanged); ButtonBase.CanExecuteChangedHandler.SetValue(this, value); command.CanExecuteChanged += value; this.UpdateCanExecute(); }

Secondly, let’s check why no such problem with RoutedUICommand approaches. The key point is it uses weak reference during hock the command’s CanExecuteChanged event.

RoutedCommand:  
public event EventHandler CanExecuteChanged
{  
    add{   CommandManager.RequerySuggested += value;}  
    remove {CommandManager.RequerySuggested -= value;}  
}  
  
  
CommandManager:  
public static event EventHandler RequerySuggested
{  
    add  {  CommandManager.AddWeakReferenceHandler(ref CommandManager.Current._requerySuggestedHandlers, value);}  
    remove  {   CommandManager.RemoveWeakReferenceHandler(CommandManager.Current._requerySuggestedHandlers, value); }  
}