【问题标题】:Setting ContentPresenter with MultiDataTrigger produces memory leak使用 MultiDataTrigger 设置 ContentPresenter 会产生内存泄漏
【发布时间】:2015-03-11 14:03:11
【问题描述】:

我有一个 ListBox,它的 Item 的 ContentPresenter 由 MultiDataTrigger 更改,具体取决于一些 IsTool 和 IsAlerting 布尔属性:

<Grid>
    <Grid.Resources>
        <DataTemplate x:Key="LayerTemplate">
            <ContentControl x:Name="contentControl" Style="{DynamicResource PageHeaderContentControlStyle}">
                <layers:PageHeader/>
            </ContentControl>
        </DataTemplate>

        <DataTemplate x:Key="ToolTemplate">
            <ContentControl x:Name="contentControl" Style="{DynamicResource ToolHeaderContentControlStyle}">
                <layers:ToolHeader/>
            </ContentControl>
        </DataTemplate>

        <DataTemplate x:Key="LayerAlertTemplate">
            <ContentControl x:Name="contentControl" Style="{DynamicResource PageHeaderAlertContentControlStyle}">
                <layers:PageHeader/>
            </ContentControl>
        </DataTemplate>

        <DataTemplate x:Key="ToolAlertTemplate">
            <ContentControl x:Name="contentControl" Style="{DynamicResource ToolHeaderAlertContentControlStyle}">
                <layers:ToolHeader/>
            </ContentControl>
        </DataTemplate>

        <Style TargetType="{x:Type ListBoxItem}" x:Key="EmptyListViewSelection">
            <Setter Property="Background" Value="Transparent" />
            <Setter Property="FocusVisualStyle" Value="{x:Null}"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type ListBoxItem}">
                        <Border BorderBrush="Transparent"
                                BorderThickness="0"
                                Background="{TemplateBinding Background}"
                                Margin="2"
                                FocusVisualStyle="{x:Null}">
                            <ContentPresenter DataContext="{Binding}" Name="contentPresenter"/>
                        </Border>
                        <ControlTemplate.Triggers>
                            <DataTrigger Binding="{Binding IsTool}" Value="False">
                                <Setter TargetName="contentPresenter" Property="ContentTemplate" Value="{StaticResource LayerTemplate}"/>
                            </DataTrigger>
                            <DataTrigger Binding="{Binding IsTool}" Value="True">
                                <Setter TargetName="contentPresenter" Property="ContentTemplate" Value="{StaticResource ToolTemplate}"/>
                            </DataTrigger>
                            <MultiDataTrigger>
                                <MultiDataTrigger.Conditions>
                                    <Condition Binding="{Binding IsTool}" Value="False"/>
                                    <Condition Binding="{Binding IsAlerting}" Value="True"/>
                                </MultiDataTrigger.Conditions>
                                <MultiDataTrigger.Setters>
                                    <Setter TargetName="contentPresenter" Property="ContentTemplate" Value="{StaticResource LayerAlertTemplate}"/>
                                </MultiDataTrigger.Setters>
                            </MultiDataTrigger>
                            <MultiDataTrigger>
                                <MultiDataTrigger.Conditions>
                                    <Condition Binding="{Binding IsTool}" Value="True"/>
                                    <Condition Binding="{Binding IsAlerting}" Value="True"/>
                                </MultiDataTrigger.Conditions>
                                <MultiDataTrigger.Setters>
                                    <Setter TargetName="contentPresenter" Property="ContentTemplate" Value="{StaticResource ToolAlertTemplate}"/>
                                </MultiDataTrigger.Setters>
                            </MultiDataTrigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Grid.Resources>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <ListBox Name="listView" 
        BorderThickness="0"
        ItemsSource="{Binding Pages}"
        SelectedValue="{Binding SelectedPage}"
        ScrollViewer.HorizontalScrollBarVisibility="Hidden" 
        ScrollViewer.VerticalScrollBarVisibility="Hidden"
        ItemContainerStyle="{StaticResource EmptyListViewSelection}"
        IsTabStop="False"
        FocusVisualStyle="{x:Null}"
        Focusable="False" Background="{x:Null}">
        <ListBox.ItemsPanel>
            <ItemsPanelTemplate>
                <WrapPanel Orientation="Horizontal" MaxWidth="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBox}}, Path=ActualWidth}" />
            </ItemsPanelTemplate>
        </ListBox.ItemsPanel>
    </ListBox>
    <GroupBox Name="pageBox" IsEnabled="{Binding Visible}" Grid.Row="1" DataContext="{Binding Path=SelectedValue, ElementName=listView}">
        <GroupBox.Header>
            <Label Content="{Binding Name}" Padding="0"/>
        </GroupBox.Header>
        <ContentControl Content="{Binding}"/>
    </GroupBox>
</Grid>

如果 IsAlerting 属性更改通知(通过 INotifyPropertyChange)大约每秒触发一次,我的应用程序的内存使用量在 15 分钟内从 230MB 增加到 804MB,应用程序最终崩溃并出现以下调用堆栈:

    Exception Info: System.Reflection.TargetInvocationException
Stack:
   at System.Windows.FrameworkTemplate.LoadTemplateXaml(System.Xaml.XamlReader, System.Xaml.XamlObjectWriter)
   at System.Windows.FrameworkTemplate.LoadTemplateXaml(System.Xaml.XamlObjectWriter)
   at System.Windows.FrameworkTemplate.LoadOptimizedTemplateContent(System.Windows.DependencyObject, System.Windows.Markup.IComponentConnector, System.Windows.Markup.IStyleConnector, System.Collections.Generic.List`1<System.Windows.DependencyObject>, System.Windows.UncommonField`1<System.Collections.Hashtable>)
   at System.Windows.FrameworkTemplate.LoadContent(System.Windows.DependencyObject, System.Collections.Generic.List`1<System.Windows.DependencyObject>)
   at System.Windows.StyleHelper.ApplyTemplateContent(System.Windows.UncommonField`1<System.Collections.Specialized.HybridDictionary[]>, System.Windows.DependencyObject, System.Windows.FrameworkElementFactory, Int32, System.Collections.Specialized.HybridDictionary, System.Windows.FrameworkTemplate)
   at System.Windows.FrameworkTemplate.ApplyTemplateContent(System.Windows.UncommonField`1<System.Collections.Specialized.HybridDictionary[]>, System.Windows.FrameworkElement)
   at System.Windows.FrameworkElement.ApplyTemplate()
   at System.Windows.FrameworkElement.MeasureCore(System.Windows.Size)
   at System.Windows.UIElement.Measure(System.Windows.Size)
   at System.Windows.ContextLayoutManager.UpdateLayout()
   at System.Windows.ContextLayoutManager.UpdateLayoutCallback(System.Object)
   at System.Windows.Media.MediaContext+InvokeOnRenderCallback.DoWork()
   at System.Windows.Media.MediaContext.FireInvokeOnRenderCallbacks()
   at System.Windows.Media.MediaContext.RenderMessageHandlerCore(System.Object)
   at System.Windows.Media.MediaContext.RenderMessageHandler(System.Object)
   at System.Windows.Threading.ExceptionWrapper.InternalRealCall(System.Delegate, System.Object, Int32)
.............

有什么想法吗?

【问题讨论】:

  • 你想做什么?为什么要每秒更改 IsAlerting 50 次?用户如何处理每秒 50 次变化的显示?
  • 我每秒更新 IsAlerting 属性 50 次仅用于测试(在现实世界中 IsAlerting 每小时更改一次)。
  • 那有什么问题呢?每秒 50 次是人为的不切实际的测试条件。
  • 我弄错了,不是每秒50次,而是每秒1次。另外,我在想,如果我正在做与大量内存分配相关的事情,我应该定期调用 GC.Collect(),但在这种情况下似乎没有帮助。
  • 手动 GC 几乎永远不是答案。我会用 Visiblity 来解决这个问题。让 XAML 布局两者,但都在同一行和列中,并且只有适当的可见性。如果值没有改变,一般不要调用 Notify。

标签: wpf


【解决方案1】:

由于某种原因,将 MultiDataTrigger 移动到单独的样式解决了问题(不再存在内存泄漏):

<Style TargetType="{x:Type ContentControl}" x:Key="PageToolHeaderContentControlThemeStyle">
    <Setter Property="Foreground" Value="{StaticResource ForegroundBrush}"/>
    <Setter Property="Padding" Value="5,3"/>
         ....
                <ControlTemplate.Triggers>
                    <MultiDataTrigger>
                        <MultiDataTrigger.Conditions>
                            <Condition Binding="{Binding Alert}" Value="Warning"/>
                            <Condition Binding="{Binding IsAcknowledged}" Value="True"/>
                        </MultiDataTrigger.Conditions>
                        <MultiDataTrigger.Setters>
                            <Setter TargetName="PART_BorderAlarmFrame" Property="Stroke" Value="Orange"/>
                            <Setter TargetName="PART_BorderAlarmFrame" Property="Visibility" Value="Visible"/>
                        </MultiDataTrigger.Setters>
                    </MultiDataTrigger>
                    <MultiDataTrigger>
                        <MultiDataTrigger.Conditions>
                            <Condition Binding="{Binding Alert}" Value="Warning"/>
                            <Condition Binding="{Binding IsAcknowledged}" Value="False"/>
                        </MultiDataTrigger.Conditions>
                        <MultiDataTrigger.Setters>
                            <Setter TargetName="PART_BorderAlarmFrame" Property="Visibility" Value="Visible"/>
                        </MultiDataTrigger.Setters>
                        <MultiDataTrigger.EnterActions>
                            <BeginStoryboard>
                                <Storyboard>
                                    <ColorAnimation Storyboard.TargetName="BackgroundBrush"
                                                    Storyboard.TargetProperty="Color"
                                                    From="Transparent" To="Orange" Duration="0:0:0.5"
                                                    AutoReverse="True" 
                                                    RepeatBehavior="Forever" />
                                </Storyboard>
                            </BeginStoryboard>
                        </MultiDataTrigger.EnterActions>
                    </MultiDataTrigger>
                    <MultiDataTrigger>
                        <MultiDataTrigger.Conditions>
                            <Condition Binding="{Binding Alert}" Value="Alarm"/>
                            <Condition Binding="{Binding IsAcknowledged}" Value="True"/>
                        </MultiDataTrigger.Conditions>
                        <MultiDataTrigger.Setters>
                            <Setter TargetName="PART_BorderAlarmFrame" Property="Stroke" Value="Red"/>
                            <Setter TargetName="PART_BorderAlarmFrame" Property="Visibility" Value="Visible"/>
                        </MultiDataTrigger.Setters>
                    </MultiDataTrigger>
                    <MultiDataTrigger>
                        <MultiDataTrigger.Conditions>
                            <Condition Binding="{Binding Alert}" Value="Alarm"/>
                            <Condition Binding="{Binding IsAcknowledged}" Value="False"/>
                        </MultiDataTrigger.Conditions>
                        <MultiDataTrigger.Setters>
                            <Setter TargetName="PART_BorderAlarmFrame" Property="Visibility" Value="Visible"/>
                        </MultiDataTrigger.Setters>
                        <MultiDataTrigger.EnterActions>
                            <BeginStoryboard>
                                <Storyboard>
                                    <ColorAnimation Storyboard.TargetName="BackgroundBrush"
                                                    Storyboard.TargetProperty="Color"
                                                    From="Transparent" To="Red" Duration="0:0:0.5"
                                                    AutoReverse="True" 
                                                    RepeatBehavior="Forever" />
                                </Storyboard>
                            </BeginStoryboard>
                        </MultiDataTrigger.EnterActions>
                    </MultiDataTrigger>
                    <Trigger Property="IsMouseOver" Value="True">
                        <Setter Property="Foreground" Value="{DynamicResource LightForegroundBrush}"/>
                        <Setter TargetName="PART_BorderDefaultFrame" Property="Stroke" Value="{StaticResource ButtonPressedBackgroundBrush}"/>
                        <Setter TargetName="PART_Border" Property="Background" Value="{StaticResource ButtonHoverBackgroundBrush}"/>
                    </Trigger>
                    <DataTrigger Binding="{Binding IsSelected}" Value="True">
                        <Setter Property="Foreground" Value="{DynamicResource LightForegroundBrush}"/>
                        <Setter TargetName="PART_BorderDefaultFrame" Property="Stroke" Value="{StaticResource ButtonPressedBackgroundBrush}"/>
                        <Setter TargetName="PART_Border" Property="Background" Value="{StaticResource ButtonPressedBackgroundBrush}"/>
                    </DataTrigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

【讨论】:

    【解决方案2】:

    您的内存泄漏可能是由Can bindings create memory leaks in WPF? 中描述的情况引起的,可能会因列表框中的虚拟化而加剧(或者如果您有很多项目并且它一次分配所有项目,则可能缺少这种情况)。你有一个 System.Reflection.TargetInvocationException 的事实让我认为你正在绑定到不是 INotifyPropertyChanged 的​​东西,然后它必须使用反射来确定值是否正在改变。

    在较新版本的 Visual Studio(2013 年,可能是 2012 年)中,有一个内存分析器可以显示哪些对象正在泄漏,这可以帮助您缩小搜索范围。

    【讨论】:

      【解决方案3】:

      不确定内存泄漏,但我不会像这样更改内容模板。如果您在 ContentPresenter 上设置 ContentTemplate,则 ContentPresenter 的 ContentSource 属性(默认为“Content”)将被否决/忽略,Content 属性将为空,并且您的 ContentTemplate 不再具有数据上下文。

      最好在样式设置器中设置 ContentTemplate 并使用样式触发器来更改它。 ContentPresenter 的默认 ContentSource 将完成剩下的工作。

      我怀疑这可能会解决您的内存泄漏问题。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-07-15
        • 2019-03-12
        • 1970-01-01
        • 1970-01-01
        • 2019-09-17
        相关资源
        最近更新 更多