如何实现每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个波束。 这令人不安。 我真的想要实现平稳的运动。 我需要一个光束按照确切的时间段进行绘制。 不是这个随机的东西。 这是我到目前为止所尝试的清单(没有任何帮助):
我没有试过的东西:
所以,就是这样。 我正在求助。
编辑:这些滞后不是关于个人电脑资源 - 毫不拖延雷达可以做大约每秒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