Actually the title of this article should be entitled “How to use WeakEventManager with ICommand implementations”, but the memory leak title is more exciting (and true)

Overview

Some WPF controls, such as Buttons, are command sources. A command source has 3 properties, Command, CommandParameter and CommandTarget. When the control does it’s action (like Click for Button)it Executes the command, using the CommandParameter and CommandTarget as inputs.

WPF has provided an interface called ICommandSource to encapsulate this concept. (Although there seems to be nothing significant which consumes the interface – just classes which implement it.)

Most (all?) command sources implementations care about the CanExecute state, and usually disable themselves if CanExecute is false. The implementations listen to the CanExecuteChanged event for the command, and when it is raised query the new CanExecute status.

And that is where the issue begins – the implementations don’t unsubscribe from the event (except when the Command property changes). So if the command object has a lifetime longer than any control(s) which list to it’s CanExecuteChanged event it will cause a memory leak of the control. This is because when you register a handler for an event the delegate isn’t just a function pointer, but also include a reference to your instance.

The RoutedCommand Winkle

At this point you are probably saying to yourself “The logic seems sound, but I use commands and my app doesn’t appear to leak”.

The answer is because most apps use RoutedCommand, rather than their own implementation of ICommand. The RoutedCommand.CanExecuteChanged implementation is to be a proxy for CommandManager.RequerySuggested, which if you read the docs or look at the IL keeps a weak reference to the delegate. That allows the delegate to be GCed, and along with it the strong reference to the command source which subscribed to the event.

So to hit this you need to have a custom ICommand implementation which implements the CanExecute event the way most .NET events are implementd.

Why I Think WPF’s CanExecuteChanged Implementation Is Wrong

No doubt someone on the WPF team thought they were being rather smart –after since most RoutedCommands are declared as static fields their lifetime is roughly the application’s lifetime. But I don’t think they were clever enough – because they redefined the standard contract for a .NET event.

Now anyone subscribing with RoutedCommand.CanExecuteChanged needs to be aware of this non-standard behavior – since if they don’t hold a strong reference to their delegate then it will be GCed and at some point the event seems to stop working. (A really fun problem to debug BTW)

Since they also have this nice ICommand interface which RoutedCommand is one implementation it also means that if you implement an ICommand you have to be aware of this behavior and also implement your event in a similar way holding weak references to the delegate, because the built-in consumers of the event (Button, etc…) rely on this behavior.

Furthermore since RoutedCommand.CanExecuteChanged event is really CommandManager.RequerySuggested each RoutedCommand instance shares the same event. This means when the event is raised the sender field is null. Another inconsistency from regular .NET event behavior.

To me this is a big mess, and unfortunately one we probably will have to live with for the whole lifetime of WPF. Why I consider it a mess is not the technical specifics of the solution, but because they redefined the standard .NET event contract.

While we could argue that the .NET event contract has flaws, the fact of the matter is it is now a standard. And this means that both producers and consumers of that contact know how to correctly implement their end.

What RoutedCommand.CanExecuteChanged has done is made a different contract where it holds weak references to the delegate. This means that producers and consumers need to be coded differently that a standard .NET event. That is the issue from my point of view.

If the WPF team has decided to do their own custom Observer pattern implementation that didn’t overload “event” I would be happy, since it would clearly be a different contract. By this I mean imagine if on ICommand instead of defining the CanExecuteChanged event they did something like this:

interface ICommand

{

void RegisterCanExecuteListener(EventHandler listener);

void UnregisterCanExecuteListener(EventHandler listener);

bool CanExecute(object parameter);

void Execute(object parameter);

}

Then they could clearly document the contract for the CanExecuteListener and no one would get confused with .NET event semantics.

How To Properly Implement A Command Source

Now that I’m done with my rant I’ll give you some advice on how to deal with this. Specifically in how to correctly implement your own command source implementation which will not leak regardless of the implementation of ICommand.

The short answer is to use WeakEventManager.

Unfortunately we need the long answer because since RoutedCommand.CanExecuteChanged doesn’t follow the standard contract it doesn’t work with WeakEventManager. Specifically because:

· WeakEventManager doesn’t hold a strong reference to the delegate, so it will be GCed

· Since the sender parameter is null, WeakEventManager can’t which listener to deliver the event to

Notice the impact of not following the standard pattern means you can't use components designed to work with the standard pattern. 

The key is in the WeakEventManager implementation to use a proxy class for the ICommand implementation. This proxy class does 2 things:

· Holds a strong reference to the delegate registered with ICommand.CanExecuteChanged

· Since there is a 1:1 relationship between the proxy and the ICommand, and the handler delegate has a reference to the proxy we can get the reference to the associated ICommand

The complete implementation of a WeakEventManager which works with all ICommand implementations is in the attached file. The class is called CommandCanExecuteChangedWeakEventManager.

Then all the command source implementation needs to do is to use that and all will be well.

The Attached Project

If you build and run the attached project you will see a couple of buttons. One demonstrates the memory leak using Button. Simply open task manager, click the button and watch memory usage go up. Click the GC button to ensure yourself that the objects really have a strong reference back to them.

The other parts of the UI are to demonstrate that CommandCanExecuteChangedWeakEventManager really works, both to deliver events and to avoid the memory leak.

Use at your own risk and other standard disclaimers apply.

Enjoy

CommandWeakEventManager.zip

WPF ICommandSource Implementations Leak Memory!的更多相关文章

  1. (转载)ASP.NET Quiz Answers: Does Page.Cache leak memory?

    原文地址:http://blogs.msdn.com/b/tess/archive/2006/08/11/695268.aspx "We use Page.Cache to store te ...

  2. Memory Leak Detection in C++

    原文链接:http://www.linuxjournal.com/article/6556?page=0,0 An earlier article [“Memory Leak Detection in ...

  3. Impossible WPF Part 2: Binding Expressions

    原文 http://www.11011.net/wpf-binding-expressions Back in April I posted an idea for building an expre ...

  4. WPF应用程序内存泄漏的一些原因

    原文:Finding Memory Leaks in WPF-based applications There are numbers of blogs that folks wrote about ...

  5. WPF常见内存泄露

    Event handlers leak This type of leak occurs when subscribing an object (let's call it listener) to ...

  6. On Memory Leaks in Java and in Android.

    from:http://chaosinmotion.com/blog/?p=696 Just because it's a garbage collected language doesn't mea ...

  7. 再谈.net的堆和栈---.NET Memory Management Basics

    .NET Memory Management Basics .NET memory management is designed so that the programmer is freed fro ...

  8. eclipse配置和使用memory Analyse分析内存

    1. 安装 在Eclipse help -> Eclipse Marketplace下搜索Memory:  图 1-1 搜索MAT插件 按照步骤安装完成重启即可. 2. 测试代码准备 测试代码 ...

  9. memory leak-----tomcat日志warn

    web应用借助于结构:spring mvc + quartz结构,部署到tomcat容器时,shutdown时的error信息: appears to have started a thread na ...

随机推荐

  1. leetcode第一刷_Combinations

    生成组合数是初中的知识,没有人不知道. 组合数学我觉得是最有意思的数学分支,室友应该是这方面的专家,他的纸牌问题我听都听不懂.. 不知道你们是什么感觉.我以看到组合数,立即会想到全排列.这可能是由于当 ...

  2. 彻底删除Cygwin

    cygwin是一个好软件,凝聚了大家很多的心血,在win10下运行的很流畅,远比微软自己搞得那个ubuntu顺手,但它有个小问题,重装系统后,如果原来的cgywin文件夹没有删除的话,你会发现你无法删 ...

  3. [svc]jdk+tomcat部署.jforum论坛部署

    安装jdk和tomcat jdk1.7.0_13(系列)下载url 我这里用的最新的jdk. 去官网下载即可 cd /usr/local/src/ tar xf jdk-8u162-linux-x64 ...

  4. 6 Multi-Cloud Architecture Designs for an Effective Cloud

    https://www.simform.com/multi-cloud-architecture/ Enterprises increasingly want to take advantage of ...

  5. django中celery的使用

    1.什么是celery celery是一个异步任务框架,当我们的程序中存在一个比较耗时的操作时,可以启动这个异步任务框架, 将耗时操作,交给它来完成,这样节省了程序的执行时间. 2.celery的原理 ...

  6. 4.3之后的PingPong效果实现

    旧版本的Unity提供Animation编辑器来编辑物理动画. 在其下方可以设置动画是Loop或者是Pingpong等运动效果. 但是,在4.3之后,Unity的动画系统发生了较大的变化. 相信很多童 ...

  7. 【驱动】MTD子系统分析

    MTD介绍 MTD,Memory Technology Device即内存技术设备 字符设备和块设备的区别在于前者只能被顺序读写,后者可以随机访问:同时,两者读写数据的基本单元不同. 字符设备,以字节 ...

  8. 解决问题:swiper动态加载图片后无法滑动

    原因:swiper在初始化的时候会扫描swiper-wrapper下面的swiper-slide的个数,从而完成初始化,但是由于动态加载时在初始化之后的动作,所以导致无法滑动. 解决方案 1:在动态获 ...

  9. tf.constant

    tf.constant constant( value, dtype=None, shape=None, name='Const', verify_shape=False ) 功能说明: 根据 val ...

  10. 5. EM算法-高斯混合模型GMM+Lasso

    1. EM算法-数学基础 2. EM算法-原理详解 3. EM算法-高斯混合模型GMM 4. EM算法-GMM代码实现 5. EM算法-高斯混合模型+Lasso 1. 前言 前面几篇博文对EM算法和G ...