实现倒计时定时器/时钟的更有效方式



我正在为老虎车比赛编写一个小型圈计数器,作为一个小型家庭项目。我想实现一个倒计时计时器,作为测试,我已经完成了以下操作:

private Thread countdownThread;
private delegate void UpdateTimer(string update);
UpdateTimer ut;
public LapCounterForm()
{
   InitializeComponent();
   //...
   ut += updateTimer;
   countdownThread = new Thread(new ThreadStart(startCountdown));
}
private void startCountdown()
{
    Process.GetCurrentProcess().ProcessorAffinity = new IntPtr(1);
    Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High;
    Thread.CurrentThread.Priority = ThreadPriority.AboveNormal;
    System.Diagnostics.Stopwatch stopwatch = new Stopwatch();
    long time = 0;
    stopwatch.Start();
    while (stopwatch.ElapsedMilliseconds <= 5000)
    {
        time = 5000 - stopwatch.ElapsedMilliseconds;
        TimeSpan ts = TimeSpan.FromMilliseconds(time);
        ut(ts.Minutes.ToString().PadLeft(2, '0') + ":" + ts.Seconds.ToString().PadLeft(2, '0') + ":" + ts.Milliseconds.ToString().PadLeft(3, '0'));
    }
}
private void updateTimer(string text)
{
    if (this.InvokeRequired)
    {
        this.Invoke(new Action<String>(ut), new object[] { text });
    }
    else
    {
        lblCountdownClock.Text = text;
    }
}

当我启动线程时,它就工作了。我得到了我想要的5秒倒计时,但我可以看到我在这个过程中使用了大量的CPU(我的8线程i7 2600k的12%)。

我想我可以通过每10毫秒而不是每毫秒更新一次UI来大大减少这种负载,但我不知道如何做到这一点,除了在制作TimeSpan和更新UI之前使用if(time % 10 == 0),但我怀疑由于while循环,这将同样低效。

我是在重新发明轮子吗?我希望我的计时器尽可能准确(至少对于老虎机圈速记录,也许UI不需要经常更新)。

EDIT:我试着按照注释中的建议注释掉实际的字符串操作和UI更新。现在,当我启动线程时,我的整个UI都会挂起,直到线程退出,我仍然有12%的CPU使用率。我怀疑while循环占用了大量的CPU时间。

更新:我使用了Kohanz发布的多媒体计时器(此处)以及Daniel的回答。我不再使用另一个线程,我只制作了其中一个计时器对象,并有一个tick事件处理程序来计算单击开始按钮和tick事件之间的时间。我甚至可以将滴答声的周期设置为1ms,这样我就可以进行看起来很酷的倒计时,而且它显然使用了0%的CPU:)我对此很满意。

不要,不要沿着这条路走。你完全是在错误地思考这个问题。你基本上是在强迫你的线程冻结而没有任何好处。

基本上,任何游戏都是这样运作的:你有一个更新循环,每当它触发时,你就会做必要的事情。例如,如果你想知道有多少时间,你可以问某种"计时器",自从发生什么事情以来,已经过去了多少时间

这里有一个更好的处理方法:

class MyStopwatch {
    private DateTime _startTime;
    private DateTime _stopTime;
    public void start() {
        _running = true;
        _startTime = DateTime.Now;
    }
    public void stop() {
        _stopTime = DateTime.Now;
        _running = false;
    }
    public double getTimePassed() {
        if(_running) {
            return (DateTime.Now - _startTime).TotalMilliseconds;
        } else {
            return (_stopTime - _startTime).TotalMilliseconds;
        }
    }
}

虽然有点言过其实,但这表明了一种实现所需目标的方法:

public class LapTimer : IDisposable
{
    private readonly System.Diagnostics.Stopwatch _stopWatch = new System.Diagnostics.Stopwatch();
    private readonly ConcurrentDictionary<string, List<TimeSpan>> _carLapTimes = new ConcurrentDictionary<string, List<TimeSpan>>();
    private readonly Action<TimeSpan> _countdownReportingDelegate;
    private readonly TimeSpan _countdownReportingInterval;
    private System.Threading.Timer _countDownTimer;
    private TimeSpan _countdownTo = TimeSpan.FromSeconds(5);
    public LapTimer(TimeSpan countdownReportingInterval, Action<TimeSpan> countdownReporter)
    {
        _countdownReportingInterval = countdownReportingInterval;
        _countdownReportingDelegate = countdownReporter;
    }
    public void StartRace(TimeSpan countdownTo)
    {
        _carLapTimes.Clear();
        _stopWatch.Restart();
        _countdownTo = countdownTo;
        _countDownTimer = new System.Threading.Timer(this.CountdownTimerCallback, null, _countdownReportingInterval, _countdownReportingInterval);
    }
    public void RaceComplete()
    {
        _stopWatch.Stop();
        _countDownTimer.Dispose();
        _countDownTimer = null;
    }
    public void CarCompletedLap(string carId)
    {
        var elapsed = _stopWatch.Elapsed;
        _carLapTimes.AddOrUpdate(carId, new List<TimeSpan>(new[] { elapsed }), (k, list) => { list.Add(elapsed); return list; });
    }
    public IEnumerable<TimeSpan> GetLapTimesForCar(string carId)
    {
        List<TimeSpan> lapTimes = null;
        if (_carLapTimes.TryGetValue(carId, out lapTimes))
        {
            yield return lapTimes[0];
            for (int i = 1; i < lapTimes.Count; i++)
                yield return lapTimes[i] - lapTimes[i - 1];
        }
        yield break;
    }
    private void CountdownTimerCallback(object state)
    {
        if (_countdownReportingDelegate != null)
            _countdownReportingDelegate(_countdownTo - _stopWatch.Elapsed);
    }
    public void Dispose()
    {
        if (_countDownTimer != null)
        {
            _countDownTimer.Dispose();
            _countDownTimer = null;
        }
    }
}
class Program
{
    public static void Main(params string[] args)
    {
        using (var lapTimer = new LapTimer(TimeSpan.FromMilliseconds(100), remaining => Console.WriteLine(remaining)))
        {
            lapTimer.StartRace(TimeSpan.FromSeconds(5));
            System.Threading.Thread.Sleep(2000);
            lapTimer.RaceComplete();
        }
        Console.ReadLine();
    }
}

最新更新