Gra*_*ier 2 c# garbage-collection mvvm objectdisposedexception
我编写了我的第一个MVVM应用程序.当我关闭应用程序时,我常常因ObjectDisposedException而导致崩溃.应用程序窗口消失后,应用程序即将崩溃.
获取堆栈跟踪很困难(参见我的另一个问题),但最后我做了,发现我的堆栈跟踪完全包含在C#库中(kernel32!BaseThreadStart,mscorwks!Thread,mscorwks!WKS等).
此外,这种崩溃是不一致的.在我上次结账和重建之后,它停止了一段时间.然后它又回来了.一旦它开始发生,它就会不断发生,即使我"清理"并重建.但擦拭和结账有时会使它停止一段时间.
我认为发生了什么:
我认为GarbageCollector在处理我的ViewModel时做的很有趣.我的ViewModelBase类析构函数在调用析构函数时有一个WriteLine来记录,而我的4个ViewModel中只有2或3个被处理掉了,它似乎根据结账而变化(例如,当我在我的运行时,我看到一直在重复顺序,但我的同事看到不同的序列与不同的对象处置).
由于堆栈跟踪没有我的代码调用,我认为这意味着我的代码不是调用被处置对象的方法.所以这让我觉得CLR是愚蠢的.
这有意义吗?有什么方法可以让GC保持一致吗?这是红鲱鱼吗?
其他可能
有用的细节:我的所有Views和ViewModel都是在App.xaml.cs文件的Application的Startup事件处理程序中创建的.同一个处理程序将ViewModels分配给DataContexts.我不确定这是否是正确的MVVM实践(正如我所说的,我的第一个MVVM应用程序),但我不明白为什么它会导致不良行为.
如有必要,我可以粘贴代码.
Eri*_*ert 13
我的ViewModelBase类析构函数有一个WriteLine,用于在调用析构函数时记录,
那真的很糟糕.我希望你只在调试版本中启用它.
你绝对不应该永远做任何复杂的析构函数,比如创建文件句柄,操纵盘的状态,等等.这只是要求最糟糕的麻烦.析构函数应该清理非托管资源,并且不做任何其他操作.
在我的4个ViewModel中,只有2个或3个被处理,并且它似乎根据结账而变化(例如,当我在我的运行时,我看到一致的重复序列,但我的同事看到不同的序列,不同的对象被处置).
你会看到事情在不同的时间以不同的顺序发生,完全可以预料,我们将在下面看到.
正确编写析构函数是C#中最难做的事情之一; 在进程关闭之前,您在最后一轮结束期间遇到异常表示您可能做错了.
所以这让我觉得CLR是愚蠢的.
为您的错误指责工具不太可能帮助您解决问题.
在编写任何析构函数之前每个人都应该知道的事情是:
析构函数不一定与任何其他代码在同一个线程上运行.这意味着您可能会遇到竞争条件,锁定排序问题,由于内存模型不足而导致的读写操作等等.如果使用析构函数,则会自动编写多线程程序,因此必须设计程序以防止所有可能的线程问题.这是您的责任,而不是CLR的责任.如果您不愿意承担编写线程安全对象的责任,那么不要编写析构函数.
即使对象从未初始化,析构函数也会运行.完全可能的是,在分配对象并且代码在构造函数的中途之后,抛出异常.对象被分配,你没有压制终结,因此它必须被破坏.析构函数需要在未完全初始化的对象面前是健壮的.
如果一个对象位于锁定目的以确保一致的突变,并且抛出异常,并且finally块不恢复一致状态,则该对象在完成时将处于不一致状态.由于中止事务导致内部状态不一致的对象,析构函数必须是健壮的.
析构函数可以按任何顺序运行.如果你有一个彼此引用的对象树,它们都是同时死的,那么每个对象的析构函数都可以随时运行.对于内部状态指其他具有或未被破坏的对象的对象,析构函数必须是健壮的.
根据垃圾收集器,等待终结器队列上的销毁的对象是活动的.析构函数会导致先前死亡的对象暂时(我们希望!)再次活跃起来.如果你的程序逻辑依赖于死对象,你必须非常小心你的析构函数.(如果析构函数逻辑使对象永久存活,那么你手上可能会遇到很大的问题.不要这样做.)
因为等待破坏的对象是活着的,并且它们被识别为需要破坏,因为GC将它们归类为死亡,等待完成的对象在世代垃圾收集器中自动向上移动一代.这意味着垃圾收集器回收存储不会发生,直到对象第二次死亡.由于对象刚刚移动到后一代,因此可能无法长时间确定.析构函数导致短暂的内存分配变得更长寿,这在某些情况下会严重影响垃圾收集器的性能.在为一个大的,短命的对象编写一个析构函数之前要仔细考虑(或者更糟糕的是,你将要创造数百万的小型短期对象); 除非您明确禁止完成,否则无法通过gen零收集器释放具有析构函数的对象.
不保证会调用析构函数.垃圾收集器不需要在进程关闭之前运行对象的析构函数,即使已知它们已经死了.你的逻辑不能依赖于调用析构函数的正确性.很多东西可以防止析构函数被调用 - 例如,FailFast,或者堆栈溢出异常,或者有人从墙上拔出电源线.面对从未被调用的析构函数,程序必须是健壮的.
抛出未处理异常的析构函数将进程置于危险状态.如果发生这种情况,运行时引擎完全有权在整个过程中失败.(虽然不需要这样做.)析构函数绝不能抛出未处理的异常.
如果您不愿意忍受这些限制,那么首先不要编写析构函数.无论你喜欢与否,这些限制都不会消失.
| 归档时间: |
|
| 查看次数: |
773 次 |
| 最近记录: |