WPF性能下降的原因 [英] The reason behind slow performance in WPF

查看:142
本文介绍了WPF性能下降的原因的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用DrawText在WPF中创建大量文本,然后将它们添加到单个Canvas中.

I'm creating a large number of texts in WPF using DrawText and then adding them to a single Canvas.

我需要在每个MouseWheel事件中重新绘制屏幕,​​我意识到性能有点慢,所以我测量了创建对象的时间,它不到1毫秒!

I need to redraw the screen in each MouseWheel event and I realized that the performance is a bit slow, so I measured the time the objects are created and it was less than 1 milliseconds!

那么可能是什么问题?很久以前,我想我在某处读到,实际上是Rendering会花费时间,而不是创建和添加视觉效果.

So what could be the problem? A long time ago I guess I read somewhere that it actually is the Rendering that takes the time, not creating and adding the visuals.

这是我用来创建文本对象的代码,我只包括了必要的部分:

Here is the code I'm using to create the text objects, I've only included the essential parts:

public class ColumnIdsInPlan : UIElement
    {
    private readonly VisualCollection _visuals;
    public ColumnIdsInPlan(BaseWorkspace space)
    {
        _visuals = new VisualCollection(this);

        foreach (var column in Building.ModelColumnsInTheElevation)
        {
            var drawingVisual = new DrawingVisual();
            using (var dc = drawingVisual.RenderOpen())
            {
                var text = "C" + Convert.ToString(column.GroupId);
                var ft = new FormattedText(text, cultureinfo, flowdirection,
                                           typeface, columntextsize, columntextcolor,
                                           null, TextFormattingMode.Display)
                {
                    TextAlignment = TextAlignment.Left
                };

                // Apply Transforms
                var st = new ScaleTransform(1 / scale, 1 / scale, x, space.FlipYAxis(y));
                dc.PushTransform(st);

                // Draw Text
                dc.DrawText(ft, space.FlipYAxis(x, y));
            }
            _visuals.Add(drawingVisual);
        }
    }

    protected override Visual GetVisualChild(int index)
    {
        return _visuals[index];
    }

    protected override int VisualChildrenCount
    {
        get
        {
            return _visuals.Count;
        }
    }
}

此代码在每次触发MouseWheel事件时运行:

And this code is run each time the MouseWheel event is fired:

var columnsGroupIds = new ColumnIdsInPlan(this);
MyCanvas.Children.Clear();
FixedLayer.Children.Add(columnsGroupIds);

可能是罪魁祸首?

我在平移时也遇到了麻烦:

I'm also having trouble while panning:

    private void Workspace_MouseMove(object sender, MouseEventArgs e)
    {
        MousePos.Current = e.GetPosition(Window);
        if (!Window.IsMouseCaptured) return;
        var tt = GetTranslateTransform(Window);
        var v = Start - e.GetPosition(this);
        tt.X = Origin.X - v.X;
        tt.Y = Origin.Y - v.Y;
    }

推荐答案

我目前正在处理可能相同的问题,并且发现了一些非常意外的事情.我正在渲染到WriteableBitmap,并允许用户滚动(缩放)和平移以更改渲染的内容.对于缩放和平移而言,该运动似乎都很不稳定,因此我自然地认为渲染花费的时间太长.经过一番检测之后,我验证了我正在以30-60 fps的速度进行渲染.无论用户如何缩放或平移,渲染时间都不会增加,因此断断续续必须来自其他地方.

I'm currently dealing with what is likely the same issue and I've discovered something quite unexpected. I'm rendering to a WriteableBitmap and allowing the user to scroll (zoom) and pan to change what is rendered. The movement seemed choppy for both the zooming and panning, so I naturally figured the rendering was taking too long. After some instrumentation, I verified that I'm rendering at 30-60 fps. There is no increase in render time regardless of how the user is zooming or panning, so the choppiness must be coming from somewhere else.

我改为看了OnMouseMove事件处理程序.虽然WriteableBitmap每秒更新30-60次,但MouseMove事件仅每秒触发1-2次.如果减小WriteableBitmap的大小,则MouseMove事件会更频繁地触发,并且平移操作看起来更平滑.因此,断断续续实际上是MouseMove事件断断续续而不是渲染的结果(例如,WriteableBitmap正在渲染看上去相同的7-10帧,触发MouseMove事件,然后WriteableBitmap渲染新平移的图像的7-10帧等).

I looked instead at the OnMouseMove event handler. While the WriteableBitmap updates 30-60 times per second, the MouseMove event is only fired 1-2 times per second. If I decrease the size of the WriteableBitmap, the MouseMove event fires more often and the pan operation appears smoother. So the choppiness is actually a result of the MouseMove event being choppy, not the rendering (e.g. the WriteableBitmap is rendering 7-10 frames that look the same, a MouseMove event fires, then the WriteableBitmap renders 7-10 frames of the newly panned image, etc).

我尝试通过每次使用Mouse.GetPosition(this)更新WriteableBitmap时都通过轮询鼠标位置来跟踪平移操作.但是,结果相同,因为在更改为新值之前,返回的鼠标位置在7到10帧中是相同的.

I tried keeping track of the pan operation by polling the mouse position every time the WriteableBitmap updates using Mouse.GetPosition(this). That had the same result, however, because the returned mouse position would be the same for 7-10 frames before changing to a new value.

然后我尝试使用PInvoke服务GetCursorPos 像这样的SO答案来轮询鼠标位置,例如:

I then tried polling the mouse position using the PInvoke service GetCursorPos like in this SO answer eg:

[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool GetCursorPos(out POINT lpPoint);

[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
    public int X;
    public int Y;

    public POINT(int x, int y)
    {
        this.X = x;
        this.Y = y;
    }
}

这实际上起到了作用.每次调用GetCursorPos时都会返回一个新位置(鼠标移动时),因此在用户平移时,每个帧的渲染位置都稍有不同.同样的断断续续似乎正在影响MouseWheel事件,我不知道如何解决该事件.

and this actually did the trick. GetCursorPos returns a new position each time it is called (when the mouse is moving), so each frame is rendered at a slightly different position while the user is panning. The same sort of choppiness seems to be affecting the MouseWheel event, and I have no idea how to work around that one.

因此,尽管以上关于有效维护视觉树的所有建议都是好的做法,但我怀疑您的性能问题可能是由于某些因素干扰了鼠标事件的频率.就我而言,似乎由于某种原因,渲染导致Mouse事件的更新和触发速度比平常慢得多.如果找到真正的解决方案,而不是部分解决方法,我将对其进行更新.

So, while all of the above advice about efficiently maintaining your visual tree is good practice, I suspect that your performance issues may be a result of something interfering with the mouse event frequency. In my case, it appears that for some reason the rendering is causing the Mouse events to update and fire much slower than usual. I'll update this if I find a true solution rather than this partial work-around.

编辑:好的,我对此进行了深入研究,我想我现在知道发生了什么.我将用更详细的代码示例进行解释:

Edit: Ok, I dug into this a little more and I think I now understand what is going on. I'll explain with more detailed code samples:

我通过注册以处理CompositionTarget.Rendering事件来按帧渲染到我的位图,如

I am rendering to my bitmap on a per-frame basis by registering to handle the CompositionTarget.Rendering event as described in this MSDN article. Basically, it means that every time the UI is rendered my code will be called so I can update my bitmap. This is essentially equivalent to the rendering that you are doing, it's just that your rendering code gets called behind the scenes depending on how you've set up your visual elements and my rendering code is where I can see it. I override the OnMouseMove event to update some variable depending on the position of the mouse.

public class MainWindow : Window
{
  private System.Windows.Point _mousePos;
  public Window()
  {
    InitializeComponent();
    CompositionTarget.Rendering += CompositionTarget_Rendering;
  }

  private void CompositionTarget_Rendering(object sender, EventArgs e)
  {
    // Update my WriteableBitmap here using the _mousePos variable
  }

  protected override void OnMouseMove(MouseEventArgs e)
  {
    _mousePos = e.GetPosition(this);
    base.OnMouseMove(e);
  }
}

问题在于,随着渲染花费更多时间,MouseMove事件(实际上是所有鼠标事件)的调用频率降低了.当呈现代码花费15毫秒时,MouseMove事件每隔几毫秒被调用一次.当呈现代码花费30毫秒时,MouseMove事件每隔几百毫秒就会被调用一次.我的发生原因的理论是,渲染是在WPF鼠标系统更新其值并触发鼠标事件的同一线程上进行的.此线程上的WPF循环必须具有一些条件逻辑,如果渲染在一帧中花费的时间太长,它将跳过进行鼠标更新的过程.当我的渲染代码在每一帧上花费太长"时,就会出现问题.然后,界面不会因为每帧渲染花费额外的15毫秒而变慢一点,而是因为超过15毫秒的渲染时间引入了两次鼠标更新之间的数百毫秒的延迟,所以口吃了很多.

The problem is that, as the rendering takes more time, the MouseMove event (and all mouse events, really) gets called much less frequently. When the rendering code takes 15ms, the MouseMove event gets called every few ms. When the rendering code takes 30ms, the MouseMove event gets called every few hundred milliseconds. My theory on why this happens is that the rendering is happening on the same thread where the WPF mouse system updates its values and fires mouse events. The WPF loop on this thread must have some conditional logic where if the rendering takes too long during one frame it skips doing the mouse updates. The problem arises when my rendering code takes "too long" on every single frame. Then, instead of the interface appearing to slow down a little bit because the rendering is taking 15 extra ms per frame, the interface stutters greatly because that extra 15ms of render time introduces hundreds of milliseconds of lag between mouse updates.

我之前提到的PInvoke解决方法实际上绕过了WPF鼠标输入系统.每次进行渲染时,它都会直接到达源,因此使WPF鼠标输入系统饿死不再阻止我的位图正确更新.

The PInvoke workaround I mentioned before essentially bypasses the WPF mouse input system. Every time the rendering happens it goes straight to the source, so starving the WPF mouse input system no longer prevents my bitmap from updating correctly.

public class MainWindow : Window
{
  private System.Windows.Point _mousePos;
  public Window()
  {
    InitializeComponent();
    CompositionTarget.Rendering += CompositionTarget_Rendering;
  }

  private void CompositionTarget_Rendering(object sender, EventArgs e)
  {
    POINT screenSpacePoint;
    GetCursorPos(out screenSpacePoint);

    // note that screenSpacePoint is in screen-space pixel coordinates, 
    // not the same WPF Units you get from the MouseMove event. 
    // You may want to convert to WPF units when using GetCursorPos.
    _mousePos = new System.Windows.Point(screenSpacePoint.X, 
                                         screenSpacePoint.Y);
    // Update my WriteableBitmap here using the _mousePos variable
  }

  [DllImport("user32.dll")]
  [return: MarshalAs(UnmanagedType.Bool)]
  static extern bool GetCursorPos(out POINT lpPoint);

  [StructLayout(LayoutKind.Sequential)]
  public struct POINT
  {
    public int X;
    public int Y;

    public POINT(int x, int y)
    {
      this.X = x;
      this.Y = y;
    }
  }
}

但是,这种方法并不能解决其余的鼠标事件(MouseDown,MouseWheel等),而且我也不愿意将PInvoke方法用于所有鼠标输入,因此我决定最好停下来使WPF鼠标输入系统饿死.我最终要做的只是在确实需要更新时才更新WriteableBitmap.仅当某些鼠标输入影响了它时才需要更新它.因此,结果是我收到一帧鼠标输入,更新下一帧上的位图,但是由于更新时间太长了几毫秒,所以在同一帧上没有收到更多鼠标输入,然后下一帧我将收到更多鼠标输入,因为不需要再次更新位图.随着我的渲染时间的增加,这会导致线性度(合理的)性能下降,因为可变长度的帧时间只是平均水平.

This approach didn't fix the rest of my mouse events (MouseDown, MouseWheel, etc), however, and I wasn't keen on taking this PInvoke approach for all of my mouse input, so I decided I better just stop starving the WPF mouse input system. What I ended up doing was only updating the WriteableBitmap when it really needed to be updated. It only needs to be updated when some mouse input has affected it. So the result is that I receive mouse input one frame, update the bitmap on the next frame but do not receive more mouse input on the same frame because the update takes a few milliseconds too long, and then the next frame I'll receive more mouse input because the bitmap didn't need to be updated again. This produces a much more linear (and reasonable) performance degradation as my rendering time increases because the variable length frame times just sort of average out.

public class MainWindow : Window
{
  private System.Windows.Point _mousePos;
  private bool _bitmapNeedsUpdate;
  public Window()
  {
    InitializeComponent();
    CompositionTarget.Rendering += CompositionTarget_Rendering;
  }

  private void CompositionTarget_Rendering(object sender, EventArgs e)
  {
    if (!_bitmapNeedsUpdate) return;
    _bitmapNeedsUpdate = false;
    // Update my WriteableBitmap here using the _mousePos variable
  }

  protected override void OnMouseMove(MouseEventArgs e)
  {
    _mousePos = e.GetPosition(this);
    _bitmapNeedsUpdate = true;
    base.OnMouseMove(e);
  }
}

将相同的知识转换为您自己的特定情况:对于导致性能问题的复杂几何图形,我将尝试某种类型的缓存.例如,如果几何形状本身从不改变或不经常改变,请尝试将其呈现到

Translating this same knowledge to your own particular situation: for your complex geometries that lead to performance issues I would try some type of caching. For example, if the geometries themselves never change or if they don't change often, try rendering them to a RenderTargetBitmap and then add the RenderTargetBitmap to your visual tree instead of adding the geometries themselves. That way, when WPF is performing it's rendering path, all it needs to do is blit those bitmaps rather than reconstruct the pixel data from the raw geometric data.

这篇关于WPF性能下降的原因的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆