如何实现每16毫秒的平滑UI更新?

我正在尝试创造一种雷达。 雷达是VisualCollection,由360个DrawingVisual(代表雷达波束)组成。 雷达放置在Viewbox上。

class Radar : FrameworkElement
{
    private VisualCollection visuals;
    private Beam[] beams = new Beam[BEAM_POSITIONS_AMOUNT]; // all geometry calculation goes here

    public Radar()
    {
        visuals = new VisualCollection(this);

        for (int beamIndex = 0; beamIndex < BEAM_POSITIONS_AMOUNT; beamIndex++)
        {
            DrawingVisual dv = new DrawingVisual();
            visuals.Add(dv);
            using (DrawingContext dc = dv.RenderOpen())
            {
                dc.DrawGeometry(Brushes.Black, null, beams[beamIndex].Geometry);
            }
        }

        DrawingVisual line = new DrawingVisual();
        visuals.Add(line);

        // DISCRETES_AMOUNT is about 500
        this.Width = DISCRETES_AMOUNT * 2;
        this.Height = DISCRETES_AMOUNT * 2;
    }

    public void Draw(int beamIndex, Brush brush)
    {
        using (DrawingContext dc = ((DrawingVisual)visuals[beamIndex]).RenderOpen())
        {
            dc.DrawGeometry(brush, null, beams[beamIndex].Geometry);
        }
    }

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

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

每个DrawingVisual都为DrawingContext.DrawGeometry(画笔,笔,几何)预先计算了几何。 笔为空,并且笔刷是具有大约500个GradientStop的LinearGradientBrush。 刷子每隔几毫秒就会更新一次,比如16毫秒。 这就是滞后。 这里是整体逻辑。

在MainWindow()构造函数中,我创建雷达并启动后台线程:

    private Radar radar;

    public MainWindow()
    {
        InitializeComponent();

        radar = new Radar();
        viewbox.Child = radar;

        Thread t = new Thread(new ThreadStart(Run));
        t.Start();
    }

在Run()方法中有一个无限循环,其中生成随机画笔,调用Dispatcher.Invoke()并设置16 ms的延迟:

    private int beamIndex = 0;
    private Random r = new Random();
    private const int turnsPerMinute = 20;
    private static long delay = 60 / turnsPerMinute * 1000 / (360 / 2);
    private long deltaDelay = delay;

    public void Run()
    {
        int beginTime = Environment.TickCount;
        while (true)
        {
            GradientStopCollection gsc = new GradientStopCollection(DISCRETES_AMOUNT);
            for (int i = 1; i < Settings.DISCRETES_AMOUNT + 1; i++)
            {
                byte color = (byte)r.Next(255);
                gsc.Add(new GradientStop(Color.FromArgb(255, 0, color, 0), (double)i / (double)DISCRETES_AMOUNT));
            }

            LinearGradientBrush lgb = new LinearGradientBrush(gsc);
            lgb.StartPoint = Beam.GradientStarts[beamIndex];
            lgb.EndPoint = Beam.GradientStops[beamIndex];
            lgb.Freeze();

            viewbox.Dispatcher.Invoke(new Action( () =>
            {
                radar.Draw(beamIndex, lgb);
            }));

            beamIndex++;
            if (beamIndex >= BEAM_POSITIONS_AMOUNT)
            {
                beamIndex = 0;
            }

            while (Environment.TickCount - beginTime < delay) { }
            delay += deltaDelay;
        }
    }

每个Invoke()调用它执行一件简单的事情:dc.DrawGeometry(),它在当前beamIndex下重绘光束。 然而,有时看起来,就像在UI更新之前一样,radar.Draw()被调用几次,而不是每16ms绘制一个波束,它每32-64ms绘制2-4个波束。 这令人不安。 我真的想要实现平稳的运动。 我需要一个光束按照确切的时间段进行绘制。 不是这个随机的东西。 这是我到目前为止所尝试的清单(没有任何帮助):

  • 将雷达放置在帆布上;
  • 使用Task,BackgroundWorker,Timer,自定义Microtimer.dll并设置不同的线程优先级;
  • 使用不同的延迟实现方式:Environment.TickCount,DateTime.Now.Ticks,Stopwatch.ElapsedMilliseconds;
  • 将LinearGradientBrush更改为预定义的SolidColorBrush;
  • 使用BeginInvoke()而不是Invoke()并更改Dispatcher Priorities;
  • 使用InvalidateVisuals()和丑陋的DoEvents();
  • 使用BitmapCache,WriteableBitmap和RenderTargetBitmap(使用DrawingContext.DrawImage(位图);
  • 使用360多边形对象而不是360 DrawingVisuals。 这样我可以避免使用Invoke()方法。 每个多边形的Polygon.FillProperty绑定到ObservableCollection,并且实现了INotifyPropertyChanged。 如此简单的代码行{brushCollection [beamIndex] =(new created and frozen brush)}导致多边形FillProperty更新,并且UI正在重绘。 但仍然没有平稳的运动;
  • 可能还有几个我可以忘记的小变通办法。
  • 我没有试过的东西:

  • 使用工具绘制3D(视口)绘制2D雷达;
  • ...
  • 所以,就是这样。 我正在求助。

    编辑:这些滞后不是关于个人电脑资源 - 毫不拖延雷达可以做大约每秒5个整圈(移动相当快)。 最有可能的是关于多线程/用户界面/调度程序或其他我还不明白的东西。

    编辑2:附加一个.exe文件,以便您可以看到实际正在进行的操作:https://dl.dropboxusercontent.com/u/8761356/Radar.exe

    EDIT3: DispatcherTimer(DispatcherPriority.Render)也没有帮助。


    对于流畅的WPF动画,您应该使用CompositionTarget.Rendering事件。

    不需要线程或搞乱调度程序。 事件将在每个新帧之前自动触发,类似于HTML的requestAnimationFrame()

    在事件更新你的WPF场景,你就完成了!

    在MSDN上有一个完整的示例。


    您可以使用WPF Performance Suite检查一些图形瓶颈:

    http://msdn.microsoft.com/es-es/library/aa969767(v=vs.110).aspx

    穿孔器是能够向您显示性能问题的工具。 也许你正在使用低性能的VGA卡?


    while (Environment.TickCount - beginTime < delay) { }
            delay += deltaDelay;
    

    上面的序列会阻塞该线程。 使用替代的“await Task.Delay(...)”,它不会像对应的Thread.Sleep(...)那样阻塞线程。

    链接地址: http://www.djcxy.com/p/20765.html

    上一篇: How to achieve smooth UI updates every 16 ms?

    下一篇: 754 floating point numbers?