nie*_*ras 6 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.
}
Run Code Online (Sandbox Code Playgroud)
要么
override void OnRender(DrawingContext drawingContext)
{
// Many different drawingContext.Draw calls
// E.g. DrawEllipse, DrawRectangle etc.
}
Run Code Online (Sandbox Code Playgroud)
这会导致大量分配,从而导致一些不必要的垃圾收集.所以是的,我需要这个,请继续主题:).
在具有零或低托管堆分配数的WPF中绘制的选项有哪些?重用对象很好,但我还没有找到一种方法来做到这一点......或者DependencyProperty在它周围/内部没有问题和分配.
我知道WritableBitmapEx但希望有一个解决方案不涉及光栅化到预定位图,而是适当的"矢量"图形,仍然可以缩放例如.
注意:CPU使用率是一个问题,但远远低于由此引起的巨大垃圾压力.
更新:我正在寻找.NET Framework 4.5+的解决方案,如果在更高版本中有任何东西,例如4.7,可能有助于回答这个问题,那就没问题了.但它适用于桌面.NET Framework.
更新2:两个主要方案的简要说明.所有示例都已经过分析CLRProfiler,并且它清楚地表明由于这个原因而发生了大量分配,这对我们的用例来说是一个问题.请注意,这是用于传达原则而不是确切代码的示例代码.
答:这种情况如下所示.基本上,会显示一个图像,并通过自定义绘制一些叠加图形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>
Run Code Online (Sandbox Code Playgroud)
`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;
}
}
Run Code Online (Sandbox Code Playgroud)
B:第二种情况涉及绘制数据的移动"网格",例如20行100列,其中元素由边框和具有不同颜色的文本组成以显示某些状态.网格根据外部输入移动,现在每秒仅更新5-10次.30 fps会更好.此,因此,更新的2000项ObservableCollection绑在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>
Run Code Online (Sandbox Code Playgroud)
这里有很多数据绑定,值类型的盒子确实会产生大量的分配,但这不是主要的问题.这是WPF完成的分配.
WinFormsCanvas解决方案存在一些问题,特别是由于与 WPF 交互方式而导致的所谓“空域”问题WindowsFormsHost。简而言之,这意味着无法在宿主之上绘制任何 WPF 视觉效果。
这个问题可以通过认识到既然我们无论如何都必须双缓冲来解决,所以我们最好缓冲到一个WriteableBitmap可以通过控件照常绘制的对象中Image。
这可以通过使用如下所示的实用程序类来实现:
using System;
using System.Drawing;
using System.Windows;
using SWM = System.Windows.Media;
using SWMI = System.Windows.Media.Imaging;
public class GdiGraphicsWriteableBitmap
{
readonly Action<Rectangle, Graphics> m_draw;
SWMI.WriteableBitmap m_wpfBitmap = null;
Bitmap m_gdiBitmap = null;
public GdiGraphicsWriteableBitmap(Action<Rectangle, Graphics> draw)
{
if (draw == null) { throw new ArgumentNullException(nameof(draw)); }
m_draw = draw;
}
public SWMI.WriteableBitmap WriteableBitmap => m_wpfBitmap;
public bool IfNewSizeResizeAndDraw(int width, int height)
{
if (m_wpfBitmap == null ||
m_wpfBitmap.PixelHeight != height ||
m_wpfBitmap.PixelWidth != width)
{
Reset();
// Can't dispose wpf
const double Dpi = 96;
m_wpfBitmap = new SWMI.WriteableBitmap(width, height, Dpi, Dpi,
SWM.PixelFormats.Bgr24, null);
var ptr = m_wpfBitmap.BackBuffer;
m_gdiBitmap = new Bitmap(width, height, m_wpfBitmap.BackBufferStride,
System.Drawing.Imaging.PixelFormat.Format24bppRgb, ptr);
Draw();
return true;
}
return false;
}
public void Draw()
{
if (m_wpfBitmap != null)
{
m_wpfBitmap.Lock();
int width = m_wpfBitmap.PixelWidth;
int height = m_wpfBitmap.PixelHeight;
{
using (var g = Graphics.FromImage(m_gdiBitmap))
{
m_draw(new Rectangle(0, 0, width, height), g);
}
}
m_wpfBitmap.AddDirtyRect(new Int32Rect(0, 0, width, height));
m_wpfBitmap.Unlock();
}
}
// If window containing this is not shown, one can Reset to stop draw or similar...
public void Reset()
{
m_gdiBitmap?.Dispose();
m_wpfBitmap = null;
}
}
Run Code Online (Sandbox Code Playgroud)
然后将 绑定ImageSource到ImageXAML 中的 an:
<Grid x:Name="ImageContainer" SnapsToDevicePixels="True">
<Image x:Name="ImageSource"
RenderOptions.BitmapScalingMode="HighQuality" SnapsToDevicePixels="True">
</Image>
</Grid>
Run Code Online (Sandbox Code Playgroud)
并在网格上调整大小以使 WriteableBitmap 的大小匹配,例如:
public partial class SomeView : UserControl
{
ISizeChangedViewModel m_viewModel = null;
public SomeView()
{
InitializeComponent();
this.DataContextChanged += OnDataContextChanged;
}
void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (m_viewModel != null)
{
this.ImageContainer.SizeChanged -= ImageSource_SizeChanged;
}
m_viewModel = e.NewValue as ISizeChangedViewModel;
if (m_viewModel != null)
{
this.ImageContainer.SizeChanged += ImageSource_SizeChanged;
}
}
private void ImageSource_SizeChanged(object sender, SizeChangedEventArgs e)
{
var newSize = e.NewSize;
var width = (int)Math.Round(newSize.Width);
var height = (int)Math.Round(newSize.Height);
m_viewModel?.SizeChanged(width, height);
}
}
Run Code Online (Sandbox Code Playgroud)
这样,即使WriteableBitmapEx您愿意,您也可以使用 WinForms/GDI+ 进行零堆分配的绘图。请注意,您将获得DrawStringGDI+ 的大力支持,包括。MeasureString。
缺点是这是光栅化的,有时可能会出现一些插值问题。因此,请务必UseLayoutRounding="True"在父窗口/用户控件上进行设置。