【问题标题】:Millisecond precise timer in C#C#中的毫秒精确计时器
【发布时间】:2015-10-26 17:31:48
【问题描述】:

我正在使用一个计时器类(System.Timers.Timer 周围的包装器),我想用它在 WPF 窗口中显示 2 分钟倒计时,精确到每 10 毫秒。基本上,我想以mm\\:ss\\.ff 格式显示一个字符串,该字符串每10 毫秒更新一次。这是我的课程:

using System;
using System.Timers;
using Proj.Utilities.Extensions;

namespace Proj.Framework
{
/// <summary>
/// Counts down in wall time, raising events at a specified updated period.
/// </summary>
public sealed class MillisecondTimer
{
    private readonly TimeSpan _initialTime;
    private readonly Timer _timerRep;
    private TimeSpan _timeRemaining;

    /// <summary>
    /// The time remaining in the countdown.
    /// </summary>
    public TimeSpan TimeRemaining
    {
        get { return _timeRemaining; }
        private set
        {
            _timeRemaining = value;
            if (_timeRemaining <= TimeSpan.Zero)
            {
                InvokeCountDownElapsed();
            }
        }
    }

    /// <summary>
    /// True if the timer is currently counting down; false otherwise.
    /// </summary>
    public bool IsCountingDown => _timerRep.Enabled;

    /// <summary>
    /// Raised every time the update period elapses.
    /// </summary>
    public event EventHandler TimeChanged;

    /// <summary>
    /// Raised when the entire countdown elapses.
    /// </summary>
    public event EventHandler CountDownElapsed;

    /// <summary>
    /// Creates a new CountDownTimer.
    /// </summary>
    /// <param name="countDownTime">
    /// The amount of time the timer should count down for.
    /// </param>
    /// <param name="updatePeriod">
    /// The period with which the CountDownTimer should raise events.
    /// </param>
    public MillisecondTimer(TimeSpan countDownTime, TimeSpan updatePeriod)
    {
        _initialTime = countDownTime;
        _timerRep = new Timer(10) { AutoReset = true };


        AttachEventHandlers();
    }

    private void AttachEventHandlers()
    {
        AttachedElapsedEventHandler();
        AttachCountDownElapsedEventHandler();
    }

    private void AttachedElapsedEventHandler()
    {
        _timerRep.Elapsed += OnElapsed;
    }

    private void AttachCountDownElapsedEventHandler()
    {
        CountDownElapsed += OnCountDownElapsed;
    }

    private void InvokeTimeChanged()
    {
        //Defined in Proj.Utilities.Extentions
        TimeChanged.InvokeIfInstantiated(this, new EventArgs());
    }

    private void InvokeCountDownElapsed()
    {
        CountDownElapsed.InvokeIfInstantiated(this, new EventArgs());
    }

    private void OnElapsed(object sender, ElapsedEventArgs e)
    {
        TimeRemaining -= TimeSpan.FromMilliseconds(10);
        InvokeTimeChanged();
    }

    private void OnCountDownElapsed(object sender, EventArgs e)
    {
        Stop();
    }

    /// <summary>
    /// Restarts the countdown.
    /// </summary>
    public void Restart()
    {
        TimeRemaining = _initialTime;
        _timerRep.Start();
        InvokeTimeChanged();
    }

    /// <summary>
    /// Stops the countdown.
    /// </summary>
    public void Stop()
    {
        _timerRep.Stop();
    }
}
}

它确实有效,因为它完成了我期望它做的事情,但它最终太慢了。当我想让它从 10 秒开始倒计时,大约需要 15 秒。最终目标是能够以 10 毫秒的分辨率准确地从 2 分钟倒计时的课程。为什么这需要比预期更长的时间,我可以做些什么来让它更好地工作?

【问题讨论】:

  • 这里有一些相关信息stackoverflow.com/questions/3744032/…
  • "精确到每 10 毫秒" 不是 windows,我认为最小分辨率约为 15 毫秒,调度/抢占可以推得更高。您可能会看到结果(这就是为什么 10 秒看起来需要 15 秒的原因)。如果您想要一个准确的 10 秒计时器(具有可变的更新间隔),那么您需要两个计时器,一个用于整个周期,一个用于更新,或者与日期/时间进行比较,但也存在分辨率问题。

标签: c# wpf timer


【解决方案1】:

不要每次都更改您的TimeRemaining - 保持开始时间并与当前时间比较以获得TimeRemaining

TimeRemaining = (DateTime.Now - _initialTime);

这样您从Timer 得到的任何错误都不会累积,但最终会平衡。

【讨论】:

  • 完美!唯一的问题是它必须是 var now = DateTime.Now; TimeRemaining = (_initialTime - (now.TimeOfDay - _started)); InvokeTimeChanged(); 以便类型可以工作。 _started 是调用 Restart() 方法的时间。谢谢!
  • 修正错误; DateTime.Now 提供的分辨率不足以满足用户的要求。
  • @Atoms 也许不符合那个确切的规格,但大多数屏幕甚至不能 display 每 10 毫秒更改一次(60Hz = ~17MS)所以我怀疑 10 毫秒是硬要求。
【解决方案2】:

我曾经围绕多媒体计时器 API 编写了一个包装器,它可以精确到 1 毫秒,请参阅 here。未针对生产用途进行测试,因此请谨慎购买。

但是,您的方法对我来说似乎不正确。问问自己是否真的需要准确的 10 毫秒计时来显示 2 分钟间隔的倒计时。你似乎把两个责任放在计时器上。保持准确的时间并定期引发事件。计时器仅适用于这两者中的后者。使用Stopwatch 准确计时。使用计时器从秒表中获取经过的时间并定期更新显示。这样,无论计时器的准确性如何,都可以准确地保持时间。用户不会注意到计时器经过多长时间的细微差异,也不会关心它是每 10 毫秒还是 20 毫秒更新一次。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-02-23
    • 1970-01-01
    • 2016-02-05
    • 2015-09-13
    • 1970-01-01
    • 1970-01-01
    • 2021-09-30
    • 1970-01-01
    相关资源
    最近更新 更多