ReactiveUI绑定似乎阻止垃圾回收的发生。

11
我们目前正在使用ReactiveUI来构建一个相当庞大的基于WPF的Windows应用程序。一切都很顺利,直到我们发现我们的应用程序消耗了大量的内存...实际上我们所有的视图、视图模型和模型都没有被垃圾回收。
根据内存分析工具(如Jet Brains dotMemory)提供的信息,ReactiveUI似乎是主要的罪魁祸首。特别是我们在视图中配置的ReactiveUI绑定,即使我们遵循最佳实践并确保在视图停用时释放所有绑定。
以下是我们创建的一个视图示例。非常感谢您能提出我们可能存在何处问题的想法。
public partial class RunbookInputsView : IViewFor<RunbookInputsViewModel>
{
    public static readonly DependencyProperty ViewModelProperty = DependencyProperty.Register(
        "ViewModel", typeof(RunbookInputsViewModel), typeof(RunbookInputsView));

    public RunbookInputsView()
    {
        InitializeComponent();

        this.WhenActivated(d =>
        {
            d(this.OneWayBind(ViewModel, vm => vm.AddInput, v => v.AddInput.Command));                
            d(this.OneWayBind(ViewModel, vm => vm.Inputs, v => v.Inputs.ItemsSource));
        });
    }

    object IViewFor.ViewModel
    {
        get { return ViewModel; }
        set { ViewModel = (RunbookInputsViewModel)value; }
    }

    public RunbookInputsViewModel ViewModel
    {
        get { return (RunbookInputsViewModel) GetValue(ViewModelProperty); }
        set { SetValue(ViewModelProperty, value); }
    }
}

1
你是否确认停用逻辑正在执行?尝试在 WhenActivated 的其中一个调用中添加 d(Disposable.Create(()=> System.Diagnostics.Debug.WriteLine("DEACTIVATED"))) - Kent Boogaart
使用断点,看起来响应式绑定被正确地处理了。 - David Cader
我的调查现在集中在使用CreateDerivedCollection和用于选择派生集合新项的lambda上。当lambda引用我的类成员时,似乎存在内存问题。以下代码看起来没问题: source.CreateDerivedCollection(x => ViewModel(x));但是这个代码似乎有问题: source.CreateDerivedCollection(x => ViewModel(x, this.Something)); - David Cader
3
我想我终于找到了泄漏的源头,它位于ReactiveUI ViewModelViewHost类中,具体来说,当您有包含ViewModelViewHost的用户控件且该用户控件未显示(比如隐藏在展开器中)的情况下。移除ViewModelViewHost并使用DataTemplate/ContentControl替换它,一切都开始正确地进行垃圾回收。 - David Cader
1个回答

2

从问题描述中很难确定泄漏的来源。让泄漏发生一段时间后,使用 windbgWindows 调试工具 的一部分)连接到该进程(注意:您可能需要为此构建 x86x64)。

连接成功后,请输入以下命令设置 .net 调试环境:

.symfix
sxe clr
sxd av
.loadby sos clr

您可以使用!dumpheap-stat来获取每种类型的内存使用情况。输出格式如下:(我为了易读性,截断了类名称和列表。)
0:012> !dumpheap -stat
Statistics:
              MT    Count    TotalSize Class Name
000007fefa55d2e8        1           24 System.[...]TransportSinkProvider
000007fefa55ce08        1           24 System.Run[...]rtSinkProvider
000007fee7c32df0        1           24 System.LocalDataStoreHolder
000007fee7c2ff78        1           24 System.Colle[...]
000007fee7c2ece0        1           24 System.Resources.FastResourceComparer
000007fee7c2ead0        1           24 System.Resources.ManifestBasedResourceGroveler
000007fee7c2ea70        1           24 System.[...]eManagerMediator
000007fee4cc1b70        4         1216 System.Xml.XmlDocument

如果你有内存泄漏,这里将显示泄漏的对象。(应该会有很多。) 一旦确定了是什么泄漏了,你就可以执行 !dumpheap -type 来获取实际对象列表。(在此示例中,我将使用 System.Xml.XmlDocument。类型名称区分大小写,必须是完全限定的。)
0:012> !dumpheap -type System.Xml.XmlDocument
         Address               MT     Size
0000000002af9050 000007fee4cc1b70      304     
0000000002afa628 000007fee4cc1b70      304     
0000000002b0ea30 000007fee4cc1b70      304     
00000000037e2780 000007fee4cc1b70      304     

Statistics:
              MT    Count    TotalSize Class Name
000007fee4cc1b70        4         1216 System.Xml.XmlDocument

您的列表可能会更长,但概率告诉我们,任何泄漏类型的随机实例都是您感兴趣的内容。如果我们在其中一个地址上执行!do命令,我们将得到以下输出:

0:012> !do 2af9050
Name:        System.Xml.XmlDocument
MethodTable: 000007fee4cc1b70
EEClass:     000007fee4ae7f00
Size:        304(0x130) bytes
File:        C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Xml\v4.0_4.0.0.0__b77a5c561934e089\System.Xml.dll
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
000007fee4cc2b40  40004fc        8   System.Xml.XmlNode  0 instance 0000000000000000 parentNode
000007fee4cc2258  400050a       10 ...XmlImplementation  0 instance 0000000002af9180 implementation
000007fee4cc22f0  400050b       18 ....Xml.DomNameTable  0 instance 0000000002af92e0 domNameTable
[Entries removed for clarity]
000007fee4cc26f0  400052f      108 ...m.Xml.XmlResolver  0 instance 0000000000000000 resolver
000007fee7c18c48  4000530      126       System.Boolean  1 instance                0 bSetResolver
000007fee7c113e8  4000531      110        System.Object  0 instance 0000000002af9788 objLock
000007fee4cc11b0  4000532      118 ....Xml.XmlAttribute  0 instance 0000000000000000 namespaceXml

你可以使用表格中列出的任何对象来使用 !do 获取更多信息。像 System.StringSystem.Boolean 这样的类型将输出它们的实际值。如果从对象中无法确定其创建位置,则下一步可能是使用 !gcroot -nostacks 查找我们对象的引用。
0:012> !gcroot -nostacks 2af9050
HandleTable:
    00000000006117d8 (pinned handle)
    -> 0000000012a55748 System.Object[]
    -> 0000000002af9050 System.Xml.XmlDocument

Found 1 unique roots (run '!GCRoot -all' to see all roots).

还有很多其他命令,这已经太长了。 !help 命令提供了一个不错的列表。(要使用它们中的任何一个,您需要在命令前加上 !!help [command] 提供关于特定命令的详细信息。例如:!help dumpobj:)

0:012> !help dumpobj
-------------------------------------------------------------------------------
!DumpObj [-nofields] <object address>

This command allows you to examine the fields of an object, as well as learn 
important properties of the object such as the EEClass, the MethodTable, and 
the size.

You might find an object pointer by running !DumpStackObjects and choosing
from the resultant list. Here is a simple object:

    0:000> !DumpObj a79d40
    Name: Customer
    MethodTable: 009038ec
    EEClass: 03ee1b84
    Size: 20(0x14) bytes
     (C:\pub\unittest.exe)
    Fields:
          MT    Field   Offset                 Type  VT     Attr    Value Name
    009038ec  4000008        4             Customer   0 instance 00a79ce4 name
    009038ec  4000009        8                 Bank   0 instance 00a79d2c bank

Note that fields of type Customer and Bank are themselves objects, and you can 
run !DumpObj on them too. You could look at the field directly in memory using
the offset given. "dd a79d40+8 l1" would allow you to look at the bank field 
directly. Be careful about using this to set memory breakpoints, since objects
can move around in the garbage collected heap.

What else can you do with an object? You might run !GCRoot, to determine what 
roots are keeping it alive. Or you can find all objects of that type with 
"!DumpHeap -type Customer".

The column VT contains the value 1 if the field is a valuetype structure, and
0 if the field contains a pointer to another object. For valuetypes, you can 
take the MethodTable pointer in the MT column, and the Value and pass them to 
the command !DumpVC.

The abbreviation !do can be used for brevity.

The arguments in detail:
-nofields:     do not print fields of the object, useful for objects like 
                  String

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接