c# - WPF 中的低分配绘图

标签 c# wpf performance garbage-collection

我在使用 WPF 和使用 DrawingContext 时遇到了一些严重的问题,或者特别是 VisualDrawingContext 来自覆盖元素上的 OnRender 或者如果使用 DrawingVisual.RenderOpen()

问题是分配了很多。例如,它似乎在每次使用绘图上下文时分配一个 byte[] 缓冲区。

如何使用绘图上下文的示例。

using (var drawingContext = m_drawingVisual.RenderOpen())
{
    // Many different drawingContext.Draw calls
    // E.g. DrawEllipse, DrawRectangle etc.
}

override void OnRender(DrawingContext drawingContext)
{
    // Many different drawingContext.Draw calls
    // E.g. DrawEllipse, DrawRectangle etc.
}

这会导致大量分配,导致一些不需要的垃圾回收。所以是的,我需要这个,请留在主题 :)。

在托管堆分配数量为零或较少的 WPF 中有哪些选择?重用对象很好,但我还没有找到一种方法来做到这一点......或者 DependencyProperty 及其周围/内部的分配没有问题。

我确实知道 WritableBitmapEx,但希望有一个解决方案不涉及栅格化为预定义的位图,而是适当的“矢量”图形,例如仍然可以缩放。

注意:CPU 使用率是一个问题,但远不及由此造成的巨大垃圾压力。

更新:我正在寻找适用于 .NET Framework 4.5+ 的解决方案,如果更高版本中有任何内容,例如4.7 可能有助于回答这个问题,那很好。但它适用于桌面 .NET Framework。

更新 2:对两个主要场景的简要描述。所有示例都已使用 CLRProfiler 进行了分析,它清楚地表明由于此发生了大量分配,这对我们的用例来说是一个问题。请注意,这是旨在传达原则的示例代码,而不是确切的代码。

A:这种情况如下图所示。基本上,显示图像并通过自定义 DrawingVisualControl 绘制一些叠加图形,然后使用 using (var drawingContext = m_drawingVisual.RenderOpen()) 获取绘图上下文然后通过它绘制。绘制了许多椭圆、矩形和文本。这个例子还展示了一些缩放的东西,这只是为了缩放等。

<Viewbox x:Name="ImageViewbox"  VerticalAlignment="Center" HorizontalAlignment="Center">
    <Grid x:Name="ImageGrid" SnapsToDevicePixels="True" ClipToBounds="True">
        <Grid.LayoutTransform>
            <ScaleTransform x:Name="ImageTransform" CenterX="0" CenterY="0" 
                            ScaleX="{Binding ElementName=ImageScaleSlider, Path=Value}"
                            ScaleY="{Binding ElementName=ImageScaleSlider, Path=Value}" />
        </Grid.LayoutTransform>
        <Image x:Name="ImageSource" RenderOptions.BitmapScalingMode="NearestNeighbor" SnapsToDevicePixels="True"
               MouseMove="ImageSource_MouseMove" /> 
        <v:DrawingVisualControl x:Name="DrawingVisualControl" Visual="{Binding DrawingVisual}" 
                                SnapsToDevicePixels="True" 
                                RenderOptions.BitmapScalingMode="NearestNeighbor" 
                                IsHitTestVisible="False" />
    </Grid>
</Viewbox>

`DrawingVisualControl 定义为:

public class DrawingVisualControl : FrameworkElement
{
    public DrawingVisual Visual
    {
        get { return GetValue(DrawingVisualProperty) as DrawingVisual; }
        set { SetValue(DrawingVisualProperty, value); }
    }

    private void UpdateDrawingVisual(DrawingVisual visual)
    {
        var oldVisual = Visual;
        if (oldVisual != null)
        {
            RemoveVisualChild(oldVisual);
            RemoveLogicalChild(oldVisual);
        }

        AddVisualChild(visual);
        AddLogicalChild(visual);
    }

    public static readonly DependencyProperty DrawingVisualProperty =
          DependencyProperty.Register("Visual", 
                                      typeof(DrawingVisual),
                                      typeof(DrawingVisualControl),
                                      new FrameworkPropertyMetadata(OnDrawingVisualChanged));

    private static void OnDrawingVisualChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var dcv = d as DrawingVisualControl;
        if (dcv == null) { return; }

        var visual = e.NewValue as DrawingVisual;
        if (visual == null) { return; }

        dcv.UpdateDrawingVisual(visual);
    }

    protected override int VisualChildrenCount
    {
        get { return (Visual != null) ? 1 : 0; }
    }

    protected override Visual GetVisualChild(int index)
    {
        return this.Visual;
    }
}

B:第二种情况涉及绘制移动的数据“网格”,例如20 行 100 列,元素由边框和不同颜色的文本组成,以显示某些状态。网格根据外部输入移动,目前每秒仅更新 5-10 次。 30帧/秒会更好。因此,这将更新 ObservableCollection 中的 2000 个项目,该项目绑定(bind)到 ListBox(使用 VirtualizingPanel.IsVirtualizing="True")和 ItemsPanel 是一个 Canvas。我们甚至无法在我们的正常用例中展示这一点,因为它分配的太多以至于 GC 暂停变得太长和太频繁。

<ListBox x:Name="Items" Background="Black" 
     VirtualizingPanel.IsVirtualizing="True" SnapsToDevicePixels="True">
    <ListBox.ItemTemplate>
        <DataTemplate DataType="{x:Type vm:ElementViewModel}">
            <Border Width="{Binding Width_mm}" Height="{Binding Height_mm}"
                    Background="{Binding BackgroundColor}" 
                    BorderBrush="{Binding BorderColor}" 
                    BorderThickness="3">
                <TextBlock Foreground="{Binding DrawColor}" Padding="0" Margin="0"
                   Text="{Binding TextResult}" FontSize="{Binding FontSize_mm}" 
                   TextAlignment="Center" VerticalAlignment="Center" 
                   HorizontalAlignment="Center"/>
            </Border>
        </DataTemplate>
    </ListBox.ItemTemplate>
    <ListBox.ItemContainerStyle>
        <Style TargetType="{x:Type ListBoxItem}">
            <Setter Property="Canvas.Left" Value="{Binding X_mm}"/>
            <Setter Property="Canvas.Top" Value="{Binding Y_mm}"/>
        </Style>
    </ListBox.ItemContainerStyle>
    <ListBox.ItemsPanel>
        <ItemsPanelTemplate>
            <Canvas IsItemsHost="True"
                Width="{Binding CanvasWidth_mm}"
                Height="{Binding CanvasHeight_mm}"
                />
        </ItemsPanelTemplate>
    </ListBox.ItemsPanel>
</ListBox>

这里有很多数据绑定(bind),值类型的装箱确实会产生很多分配,但这不是这里的主要问题。它是由 WPF 完成的分配。

最佳答案

一些输入

您的代码段不可用,所以我只能提供建议。 在性能方面,使用 Microsoft 提供的分析工具。 您可以找到工具 here

您可以阅读的一个更重要的链接是 WPF graphics

注意:- 尝试使用绘图组

关于c# - WPF 中的低分配绘图,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44957522/

相关文章:

wpf - 模板化 wpf 控件中的内容对齐和定位

Python,SQLAlchemy,如何只用一个提交指令以有效的方式插入查询

MySQL : Selecting values from a specific column is very slow

Java:估计可用的硬件并行性指南

c# - Css 未在浏览器中执行

c# - 如何使用 DateTime.Now.ToString();在 sql 命令中,无需删除斜杠

c# - WebClient:登录 Gmail

WPF:在最右侧的 StackPanel/DockPanel 中对齐最后两个控件

c# - 将遗留专有 Web 应用程序重写为 MVC3/Entity-Code-First

.net - 自动卡住从模板构建的 wpf 对象