none
高精度Timer定时器 RRS feed

  • 常规讨论

  • Stopwatch sw = new Stopwatch();
    int interval = 20;
    int nextFrame = interval;
    sw.start();
    
    while (!is_stop)
    {
    
        if (sw.ElapsedMilliseconds >= nextFrame)
    
        {
            周期事件();
    
            nextFrame += interval;
    
        }
        DispatcherHelper.DoEvents(); //自定义类,实现类似WinForm的DoEvents
        Thread.Sleep(1); //降低计时精度节约CPU资源
    }
    这是我实现的高精度Timer,不知道有没有更合适的方法?
    要求实现最小10ms的时钟间隔,并且误差不累加的时钟。
    如果没有更好的方法了,那么我现在这个实现方案能不能打包成一个类?
    刚学面向对象,而且不太会C#的语法,所以想请高手帮我打包一下。。。
    2013年4月23日 10:17

全部回复

  • 这个方法并不能完全实现精准到10ms以下,可以试下4.0提供的System.Threading.SpinWait不过当系统烦忙的情况偶尔也会出现超过10ms的情况.

    如果你CPU核数多的时候又要非常精准无误可以考虑Thread.SpinWait...

    2013年4月23日 13:19
  • hi,

    只要是用主线程(UI)计數,基本上很难达到精准,在大专案里主线程逻辑够多,更是不准

    应该要用多线程计數,这些都是以1ms为最小单位
    System.Threading.Timer

    System.Timers.Timer

    若要使用计数器,千万要确定计数器里的逻辑运行时间不能大于计数器的间距


    秘訣無它,唯勤而已 http://www.dotblogs.com.tw/yc421206/

    2013年4月23日 15:50
  • SpinWait有点复杂,看不懂。。。StopWatch不是非常精确的么?

    我要实现的其实是播放一帧帧的位图,实现和音乐同步。

    所以只要误差不累加,几毫秒的影响就不会太大(人的感觉根本没法区别几毫秒的事情吧)。

    2013年4月23日 16:09
  • Timer不是只能实现到50ms左右么?更小的间隔据说没有意义。

    而且Timer的误差会累加啊,一次几ms,连续计时几分钟以后就会相差几秒甚至几十秒。。。

    我是初学,不太搞得懂线程,一创建多线程以后我就不知道怎么控制UI元素了,总是说对象在另一个线程无法访问。

    我的应用主要就是播放一组帧图,但是因为帧图组成的动画进度要和音乐同步,所以绝对不允许误差累加,但是偶尔出现的一两帧误差大一点其实人的感觉也不一定能分辨。

    2013年4月23日 16:13
  • 我觉得你应该新建一个线程,以Thread.Sleep(5)作为间隔循环,通过获取音乐的播放位置去计算该显示哪一幅图。

    while (!IsBreak)
    {
        //Do Something...
        Thread.Sleep(5);
    }

    2013年4月24日 2:35
  • hi,

    如果我没看错的话,楼主的标题是高精度...

    Timer 本身就会有误差没错,但 UI 上的Timer应当是最差的,那是因为 UI 线程工作的堵塞让他变差了,若是用多线程就可以避免掉这问题(但不是完全还是要依照CPU),若你觉得不需要很精准那也可以使用 UI 线程上的Timer。

    我想你碰到的是跨线程更新UI的问题
    http://www.dotblogs.com.tw/yc421206/archive/2009/02/13/7141.aspx
    http://www.dotblogs.com.tw/yc421206/archive/2011/10/10/40840.aspx

    更新UI基本上Winform是用 Invoke;WPF是用Dispatcher,以下的写法是利用BCL内建的Action委派

    //update UI
    this.Invoke((Action)(() =>
    {
        //TODO:更新UI的代码
    }));

    但基本上我不喜欢用Timer,原因是触发条件的时间很难掌握,比如说三秒钟触发一次我想要做的事,在正常情况下三秒可以完成,但在异常情况下需要三秒以上(比如 CPU 繁忙),这时候就会造成堵车,卡严重一点的你可看到UI会忽然不动,然后一下又动得很快,这时我会采用无穷循环的做法,你可参考以下:

    private bool _isExit = false;
    
    public bool IsExit
    {
        get { return _isExit; }
        set { _isExit = value; }
    }
    
    public int Interval { get; set; }
    
    private void Form1_Load(object sender, EventArgs e)
    {
    }
    
    private void button1_Click(object sender, EventArgs e)
    {
        this.IsExit = false;
        Start();
    }
    
    private void button2_Click(object sender, EventArgs e)
    {
        this.IsExit = true;
    }
    
    private long _count = 0;
    
    public void Start()
    {
        Stopwatch sw = new Stopwatch();
        Task.Factory.StartNew(() =>
        {
            while (true)
            {
                sw.Restart();
    
                //外部離開循环条件
                if (this.IsExit)
                {
                    break;
                }
    
                //TODO:想要做的事,比如更新UI
                this._count++;
                this.Invoke((Action)(() =>
                {
                    this.label1.Text = this._count.ToString();
                }));
    
                ////空转等待
                //SpinWait.SpinUntil(() =>
                //{
                //    if (sw.ElapsedMilliseconds > 10000)
                //    {
                //        return true;
                //    }
    
                //    if (this.IsExit)
                //    {
                //        return true;
                //    }
                //    return false;
                //});
    
                //空转等待
                SpinWait.SpinUntil(() =>
                {
                    return this.IsExit;
                }, 10000);
            }
        });
    }


    秘訣無它,唯勤而已 http://www.dotblogs.com.tw/yc421206/

    2013年4月24日 4:09
  • 好认真!!太感谢了,稍后我尝试一下。
    2013年4月24日 12:41
  • 有没有办法把这个无穷循环的时钟做成一个类呢?
    2013年4月24日 12:51
  • hi,

    办法有是有,我不晓得这样的东西做成一个class能做啥,因为业务逻辑是不断变动的,每次都会拆掉重写。


    秘訣無它,唯勤而已 http://www.dotblogs.com.tw/yc421206/

    2013年4月24日 13:34
  • 不能传递一个方法作为业务逻辑么?

    按照 Timer 的结构写成一个类,然后这个类和 Timer 具有相同的使用方法,唯一的区别就是这个类的计时更精确。

    2013年4月24日 17:21
  • hi,

    他并不是比较准确的Timer,它与一般Timer不同之处在于处理逻辑不同,我门可以传入委托来处理

    public class Pooling
    {
        private int _interval = 1000;
    
        public int Interval
            {
                get { return _interval; }
                set { _interval = value; }
            }
    
        public bool IsRunning { get; set; }
    
        public void Start(Action action)
        {
            if (this.IsRunning)
            {
                return;
            }
    
            this.IsRunning = true;
    
            Task.Factory.StartNew(() =>
            {
                while (this.IsRunning)
                {
                    action();
    
                    //空转等待
                    SpinWait.SpinUntil(() =>
                    {
                        return !this.IsRunning;
                    }, this.Interval);
                }
            });
        }
    
        public void Stop()
        {
            if (!this.IsRunning)
            {
                return;
            }
            this.IsRunning = false;
        }
    }

    用戶端調用

    private void button1_Click(object sender, EventArgs e)
    {
        this._pooling.Start(() =>
        {
            this._counter++;
            if (InvokeRequired)
            {
                this.Invoke((Action)(() =>
                {
                    this.label1.Text = this._counter.ToString();
                }));
    
                // this.Invoke(null);
                //Thread.Sleep(1000000);
            }
            else
            {
                this.label1.Text = this._counter.ToString();
            }
        });
    }
    
    private void button2_Click(object sender, EventArgs e)
    {
        this._pooling.Stop();
    }



    秘訣無它,唯勤而已 http://www.dotblogs.com.tw/yc421206/

    2013年4月25日 7:17
  • 感谢! 那么 SpinWait 可以保证毫秒级的精确么?根据现在的逻辑来看,如果 SpinWait 出现误差,那么这个误差是会持续累加的吧?
    2013年4月25日 18:36
  • 求救!!!这种方法制造的时钟非常不精确啊!!是因为笔记本电脑的移动处理器导致计时变慢么?

    24帧的图片,33ms的间隔,正常应该是792ms就放完了

    但是现在1000ms居然都还没结束

    2013年4月26日 16:25
  • hi,

    我并没说它不会有误差,这我不敢保证(至少我用来处理毫秒级的控制逻辑没有问题),这一切都要看 CPU 处理的能力,若你执意要每次都要有确保毫秒级的准确度,这可能只有MS能解答。

    善用google可以解你的答案
    key word:WPF InvokeRequired


    秘訣無它,唯勤而已 http://www.dotblogs.com.tw/yc421206/

    2013年4月26日 16:28
  • hi,

    那就表示可能有发生线程死结或是其他造成会耗时的处理,是你不知道的原因

    你应该先单纯化,先用单线程处理,确定逻辑没问题再改成多线程,多线程没问题在改成类的包装,你对线程控制不太了解,一下跳太大步会有问题的


    秘訣無它,唯勤而已 http://www.dotblogs.com.tw/yc421206/

    2013年4月26日 16:36
  • Class1.cs:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading;
    using System.Threading.Tasks;
    
    namespace ExactTimerClass
    {
        public class ExactTimer
        {
            public int Interval { get; set; }
            public bool IsRunning { get; set; }
            public bool AutoReset { get; set; }
    
            public ExactTimer()
            {
                this.Interval = 0;
                this.IsRunning = false;
                this.AutoReset = false;
            }
    
            public ExactTimer(int _interval)
            {
                this.Interval = _interval;
                this.IsRunning = false;
                this.AutoReset = false;
            }
    
            public ExactTimer(int _interval, bool _autoReset)
            {
                this.Interval = _interval;
                this.IsRunning = false;
                this.AutoReset = _autoReset;
            }
    
            public void Start(Action action)
            {
                if (this.IsRunning)
                {
                    return;
                }
    
                this.IsRunning = true;
    
                Task.Factory.StartNew(() =>
                {
                    while (this.IsRunning)
                    {
                        //空转等待
                        SpinWait.SpinUntil(() =>
                        {
                            return !this.IsRunning;
                        }, this.Interval);
    
                        action();
    
                        if (!this.AutoReset)
                        {
                            this.IsRunning = false;
                            break;
                        }
                    }
                });
            }
    
            public void Stop()
            {
                if (!this.IsRunning)
                {
                    return;
                }
                this.IsRunning = false;
            }
        }
    }
    

    MainWindow.xaml.cs:(readingMarker是从DLL中读入位图资源的,这个和我们现在讨论的问题无关)

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using System.Windows.Navigation;
    using System.Windows.Shapes;
    using ExactTimerClass;
    
    namespace MarkerJudge
    {
        /// <summary>
        /// MainWindow.xaml 的交互逻辑
        /// </summary>
    
        enum MarkerState { NULL, MA, Bad, Good, Great, Perfect };
    
        public partial class MainWindow : Window
        {
            private BitmapImage[] bitMarkerMA = new BitmapImage[24];
            private BitmapImage[] bitMarkerBad = new BitmapImage[16];
            private BitmapImage[] bitMarkerGood = new BitmapImage[16];
            private BitmapImage[] bitMarkerGreat = new BitmapImage[16];
            private BitmapImage[] bitMarkerPerfect = new BitmapImage[16];
    
            private BitmapImage bitButtonDown = new BitmapImage(new Uri("pack://application:,,,/Resources/test_button_down.png"));
            private BitmapImage bitButtonUp = new BitmapImage(new Uri("pack://application:,,,/Resources/test_button_up.png"));
    
            private ExactTimer TimerLoop = new ExactTimer(Properties.Settings.Default.LoopInterval, true);
            private ExactTimer TimerFrame = new ExactTimer(Properties.Settings.Default.FrameInterval, true);
    
            int _currentFrame;
            MarkerState _state;
    
            public MainWindow()
            {
                InitializeComponent();
            }
    
            private void resizeWindow()
            {
                this.Width += 180 - MainGrid.ActualWidth;
                this.Height += 180 - MainGrid.ActualHeight;
            }
    
            private void readingMarker()
            {
                string _prefix = "pack://application:,,,/marker" + 
                    Properties.Settings.Default.MarkerNo.ToString().PadLeft(2, '0');
                string _prefixMA = _prefix + ";component/MA";
                string _prefixBad = _prefix + ";component/H1";
                string _prefixGood = _prefix + ";component/H2";
                string _prefixGreat = _prefix + ";component/H3";
                string _prefixPerfect = _prefix + ";component/H4";
    
                for (int i = 0; i < 16; i++)
                {
                    string _suffix = i.ToString().PadLeft(2, '0') + ".png";
                    bitMarkerMA[i] = new BitmapImage(new Uri(_prefixMA + _suffix));
                    bitMarkerBad[i] = new BitmapImage(new Uri(_prefixBad + _suffix));
                    bitMarkerGood[i] = new BitmapImage(new Uri(_prefixGood + _suffix));
                    bitMarkerGreat[i] = new BitmapImage(new Uri(_prefixGreat + _suffix));
                    bitMarkerPerfect[i] = new BitmapImage(new Uri(_prefixPerfect + _suffix));
                }
    
                for (int i = 16; i < 24; i++)
                {
                    bitMarkerMA[i] = new BitmapImage(new Uri(_prefixMA + i.ToString().PadLeft(2, '0') + ".png"));
                }
            }
    
            private void Window_Loaded(object sender, RoutedEventArgs e)
            {
                resizeWindow();
                readingMarker();
            }
    
            private void loopElapsed()
            {
                _currentFrame = 0;
                _state = MarkerState.MA;
                this.TimerFrame.Start(() =>
                {
                    this.Dispatcher.Invoke((Action)(() =>
                    {
                        frameElapsed();
                    }));
                });
            }
    
            private void frameElapsed()
            {
                switch (_state)
                {
                    case MarkerState.NULL:
                        ImgMarker.Source = null;
                        TimerFrame.Stop();
                        break;
                    case MarkerState.MA:
                        if (_currentFrame < 24)
                        {
                            ImgMarker.Source = bitMarkerMA[_currentFrame++];
                        }
                        break;
                    /*case MarkerState.Bad:
                        if (_currentFrame < 16)
                        {
                            ImgMarker.Source = bitMarkerBad[_currentFrame++];
                        }
                        break;
                    case MarkerState.Good:
                        if (_currentFrame < 16)
                        {
                            ImgMarker.Source = bitMarkerGood[_currentFrame++];
                        }
                        break;
                    case MarkerState.Great:
                        if (_currentFrame < 16)
                        {
                            ImgMarker.Source = bitMarkerGreat[_currentFrame++];
                        }
                        break;
                    case MarkerState.Perfect:
                        if (_currentFrame < 16)
                        {
                            ImgMarker.Source = bitMarkerPerfect[_currentFrame++];
                        }
                        break;*/
                }
            }
    
            private void Window_ContentRendered(object sender, EventArgs e)
            {
                this.TimerLoop.Start(() =>
                {
                    this.Dispatcher.Invoke((Action)(() =>
                    {
                        loopElapsed();
                    }));
                });
            }
    /*
            private void Button_Down()
            {
                ImgButton.Source = bitButtonDown;
            }
    
            private void Button_Up()
            {
                ImgButton.Source = bitButtonUp;
            }
    
            private void ImgButton_MouseDown(object sender, MouseButtonEventArgs e)
            {
                Button_Down();
            }
    
            private void ImgButton_MouseUp(object sender, MouseButtonEventArgs e)
            {
                Button_Up();
            }
    
            private void ImgButton_TouchDown(object sender, TouchEventArgs e)
            {
                Button_Down();
            }
    
            private void ImgButton_TouchUp(object sender, TouchEventArgs e)
            {
                Button_Up();
            }*/
        }
    }
    

    2013年4月26日 16:43
  • 那么Stopwatch有什么缺点么?我觉得在我的这个逻辑里面,Stopwatch计时更合适,因为他不会累计误差
    2013年4月27日 6:25
  • hi,

    目前他是MS最推荐的方式,至于缺点我无法回答,这得询问MS原厂


    秘訣無它,唯勤而已 http://www.dotblogs.com.tw/yc421206/

    2013年4月27日 15:49