最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

.NET中實(shí)現(xiàn)高精度定時(shí)器的思路

 更新時(shí)間:2023年12月20日 09:27:33   作者:czwy  
一個(gè)定時(shí)器至少需要考慮三部分功能:計(jì)時(shí)、等待、觸發(fā)模式。計(jì)時(shí)是進(jìn)行時(shí)間檢查,調(diào)整等待的時(shí)間;等待則是用來(lái)跳過(guò)指定的時(shí)間間隔。觸發(fā)模式是指定時(shí)器每次Tick的時(shí)間固定還是每次定時(shí)任務(wù)時(shí)間間隔固定,本文將討論高精度定時(shí)器實(shí)現(xiàn)的思路,一起看看吧

.NET中有多少種定時(shí)器一文介紹過(guò).NET中至少有6種定時(shí)器,但精度都不是特別高,一般在15ms~55ms之間。在一些特殊場(chǎng)景,可能需要高精度的定時(shí)器,這就需要我們自己實(shí)現(xiàn)了。本文將討論高精度定時(shí)器實(shí)現(xiàn)的思路。

高精度定時(shí)器

一個(gè)定時(shí)器至少需要考慮三部分功能:計(jì)時(shí)、等待、觸發(fā)模式。計(jì)時(shí)是進(jìn)行時(shí)間檢查,調(diào)整等待的時(shí)間;等待則是用來(lái)跳過(guò)指定的時(shí)間間隔。觸發(fā)模式是指定時(shí)器每次Tick的時(shí)間固定還是每次定時(shí)任務(wù)時(shí)間間隔固定。比如定時(shí)器時(shí)間間隔10ms,定時(shí)任務(wù)耗時(shí)7ms,是每隔10ms觸發(fā)一次定時(shí)任務(wù),還是等定時(shí)任務(wù)執(zhí)行完后等10ms再觸發(fā)下一個(gè)定時(shí)任務(wù)。

計(jì)時(shí)

Windows提供了可用于獲取高精度時(shí)間戳或者測(cè)量時(shí)間間隔的API。系統(tǒng)原生API是QueryPerformanceCounter (QPC)。在.NET種提供了System.Diagnostics.Stopwatch類(lèi)獲取高精度時(shí)間戳,它內(nèi)部也是通過(guò)QueryPerformanceCounter (QPC)進(jìn)行高精度計(jì)時(shí)。
QueryPerformanceCounter (QPC)使用硬件計(jì)數(shù)器作為其基礎(chǔ)。硬件計(jì)時(shí)器由三個(gè)部分組成:時(shí)鐘周期生成器、計(jì)數(shù)時(shí)鐘周期的計(jì)數(shù)器和檢索計(jì)數(shù)器值的方法。這三個(gè)分量的特征決定了QueryPerformanceCounter (QPC)的分辨率、精度、準(zhǔn)確性和穩(wěn)定性[1]。它的精度可以高達(dá)幾十納秒,用來(lái)實(shí)現(xiàn)高精度定時(shí)器基本沒(méi)什么問(wèn)題。

等待

等待策略通常有兩種:

  • 自旋:讓CPU空轉(zhuǎn)等待,一直占用CPU時(shí)間。
  • 阻塞:讓線程進(jìn)入阻塞狀態(tài),出讓CPU時(shí)間片,滿足等待時(shí)間后切換回運(yùn)行狀態(tài)。

自旋等待

自旋等待可以使用Thread.SpinWait(int iteration)來(lái)實(shí)現(xiàn),參數(shù)iteration是迭代次數(shù)。由于CPU速度可能是動(dòng)態(tài)的,所以很難根據(jù)iteration計(jì)算消耗的時(shí)間,最好是結(jié)合Stopwatch使用:

void Spin(Stopwatch w, int duration)
{
    var current = w.ElapsedMilliseconds;
    while ((w.ElapsedMilliseconds - current) < duration)
        Thread.SpinWait(5);
}

由于自旋是以消耗CPU為代價(jià)的,上述代碼運(yùn)行時(shí),CPU處于滿負(fù)荷工作狀態(tài)(使用率持續(xù)保持100%左右),因此短暫的等待可以考慮自旋,長(zhǎng)時(shí)間運(yùn)行的定時(shí)器不太建議使用該方法。

阻塞等待

阻塞等待需要操作系統(tǒng)能夠及時(shí)把定時(shí)器線程調(diào)度回運(yùn)行狀態(tài)。默認(rèn)情況下,Windows的系統(tǒng)的計(jì)時(shí)器精度為15ms左右。如果是線程阻塞,出讓其時(shí)間片進(jìn)行等待,然后再被調(diào)度運(yùn)行的時(shí)間至少是一個(gè)時(shí)間切片15ms左右。要通過(guò)阻塞實(shí)現(xiàn)高精度計(jì)時(shí),則需要減少時(shí)間切片的長(zhǎng)度。Windows系統(tǒng)API提供了timeEndPeriod可以把計(jì)時(shí)器精度修改到1ms,在使用計(jì)時(shí)器服務(wù)之前立即調(diào)用timeEndPeriod,并在使用完計(jì)時(shí)器服務(wù)后立即調(diào)用timeEndPeriodtimeEndPeriodtimeEndPeriod必須成對(duì)出現(xiàn)。

在Windows 10, version 2004之前,timeEndPeriod會(huì)影響全局Windows設(shè)置,所有進(jìn)程都會(huì)使用修改后的計(jì)時(shí)精度。從Windows 10, version 2004開(kāi)始,只有調(diào)用timeEndPeriod的進(jìn)程收到影響。
設(shè)置更高的精度可以提高等待函數(shù)中超時(shí)間隔的準(zhǔn)確性。 但是,它也可能會(huì)降低整體系統(tǒng)性能,因?yàn)榫€程計(jì)劃程序更頻繁地切換任務(wù)。 高精度還可以阻止 CPU 電源管理系統(tǒng)進(jìn)入節(jié)能模式。 設(shè)置更高的分辨率不會(huì)提高高分辨率性能計(jì)數(shù)器的準(zhǔn)確性。

通常我們使用Thread.Sleep來(lái)掛起線程等待,Sleep的參數(shù)最小為1ms,但實(shí)際上很不穩(wěn)定,實(shí)測(cè)發(fā)現(xiàn)大部分時(shí)候穩(wěn)定在阻塞2ms。我們可以采用Sleep(0)或者Thread.Yield結(jié)合Stopwatch計(jì)時(shí)的方式修正。

void wait(Stopwatch w, int duration)
{
    var current = w.ElapsedMilliseconds;
    while ((w.ElapsedMilliseconds - current) < duration)
        Thread.Sleep(0);
}

Thread.Sleep(0)和Thread.Yield在 CPU 高負(fù)載情況下非常不穩(wěn)定,可能會(huì)產(chǎn)生更多的誤差。因此誤差修正最好通過(guò)自旋方式實(shí)現(xiàn)。

還有一種阻塞的方式是多媒體定時(shí)器timeSetEvent,也是網(wǎng)上關(guān)于高精度定時(shí)器提得比較多的一種方式。它是winmm.dll中的函數(shù),穩(wěn)定性和精度都比較高,能提供1ms的精度。
官方文檔中說(shuō)timeSetEvent是一個(gè)過(guò)時(shí)的方法,建議使用CreateTimerQueueTimer替代[3]。但CreateTimerQueueTimer的精度和穩(wěn)定性都不如多媒體定時(shí)器,所以在需要高精度定時(shí)器時(shí),還是要用timeSetEvent。以下是封裝多媒體定時(shí)器的例子

public enum TimerError
{
    MMSYSERR_NOERROR = 0,
    MMSYSERR_ERROR = 1,
    MMSYSERR_INVALPARAM = 11,
    MMSYSERR_NOCANDO = 97,
}
public enum RepeateType
{
    TIME_ONESHOT=0x0000,
    TIME_PERIODIC = 0x0001
}
public enum CallbackType
{
    TIME_CALLBACK_FUNCTION = 0x0000,
    TIME_CALLBACK_EVENT_SET = 0x0010,
    TIME_CALLBACK_EVENT_PULSE = 0x0020,
    TIME_KILL_SYNCHRONOUS = 0x0100
}
public class HighPrecisionTimer
{
    private delegate void TimerCallback(int id, int msg, int user, int param1, int param2);
    [DllImport("winmm.dll", EntryPoint = "timeGetDevCaps")]
    private static extern TimerError TimeGetDevCaps(ref TimerCaps ptc, int cbtc);
    [DllImport("winmm.dll", EntryPoint = "timeSetEvent")]
    private static extern int TimeSetEvent(int delay, int resolution, TimerCallback callback, int user, int eventType);
    [DllImport("winmm.dll", EntryPoint = "timeKillEvent")]
    private static extern TimerError TimeKillEvent(int id);
    private static TimerCaps _caps;
    private int _interval;
    private int _resolution;
    private TimerCallback _callback;
    private int _id;
    static HighPrecisionTimer()
    {
        TimeGetDevCaps(ref _caps, Marshal.SizeOf(_caps));
    }
    public HighPrecisionTimer()
    {
        Running = false;
        _interval = _caps.periodMin;
        _resolution = _caps.periodMin;
        _callback = new TimerCallback(TimerEventCallback);
    }
    ~HighPrecisionTimer()
    {
        TimeKillEvent(_id);
    }
    public int Interval
    {
        get { return _interval; }
        set
        {
            if (value < _caps.periodMin || value > _caps.periodMax)
                throw new Exception("invalid Interval");
            _interval = value;
        }
    }
    public bool Running { get; private set; }
    public event Action Ticked;
    public void Start()
    {
        if (!Running)
        {
            _id = TimeSetEvent(_interval, _resolution, _callback, 0,
                (int)RepeateType.TIME_PERIODIC | (int)CallbackType.TIME_KILL_SYNCHRONOUS);
            if (_id == 0) throw new Exception("failed to start Timer");
            Running = true;
        }
    }
    public void Stop()
    {
        if (Running)
        {
            TimeKillEvent(_id);
            Running = false;
        }
    }
    private void TimerEventCallback(int id, int msg, int user, int param1, int param2)
    {
        Ticked?.Invoke();
    }
}

觸發(fā)模式

由于定時(shí)任務(wù)執(zhí)行時(shí)間不確定,并且可能耗時(shí)超過(guò)定時(shí)時(shí)間間隔,定時(shí)器的觸發(fā)可能會(huì)有三種模式:固定時(shí)間框架,可推遲時(shí)間框架,固定等待時(shí)間。

  • 固定時(shí)間框架:盡量按照設(shè)定的時(shí)間來(lái)執(zhí)行任務(wù),只要任務(wù)不是始終超時(shí),就可以回到原來(lái)的時(shí)間框架上
  • 可推遲時(shí)間框架:也是盡量按照設(shè)定的時(shí)間執(zhí)行任務(wù),但是超時(shí)的任務(wù)會(huì)推遲時(shí)間框架。
  • 固定等待時(shí)間:不管任務(wù)執(zhí)行時(shí)長(zhǎng),每次任務(wù)執(zhí)行結(jié)束到下一次任務(wù)開(kāi)始執(zhí)行間的等待時(shí)間固定。

假定時(shí)間間隔為10ms,任務(wù)執(zhí)行的時(shí)間在7~11ms之間,下圖中顯示了三種觸發(fā)模式的區(qū)別。

其實(shí)還有一種觸發(fā)模式:任務(wù)執(zhí)行時(shí)長(zhǎng)大于時(shí)間間隔時(shí),只要時(shí)間間隔一到,就執(zhí)行定時(shí)任務(wù),多個(gè)定時(shí)任務(wù)并發(fā)執(zhí)行。之所以這里沒(méi)有提及這種模式,是因?yàn)樵诟呔榷〞r(shí)場(chǎng)景中,執(zhí)行任務(wù)的時(shí)間開(kāi)銷(xiāo)很有可能大于定時(shí)器的時(shí)間間隔,如果開(kāi)啟新線程執(zhí)行定時(shí)任務(wù),可能會(huì)占用大量線程,這個(gè)需要結(jié)合實(shí)際情況考慮如何執(zhí)行定時(shí)任務(wù)。這里討論的是默認(rèn)在定時(shí)器線程上執(zhí)行定時(shí)任務(wù)。

到此這篇關(guān)于.NET中如何實(shí)現(xiàn)高精度定時(shí)器的文章就介紹到這了,更多相關(guān).NET定時(shí)器內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論

静宁县| 资溪县| 灵寿县| 林口县| 中西区| 吉安市| 左贡县| 阿克苏市| 巫溪县| 侯马市| 安图县| 嘉义市| 连山| 榆树市| 房产| 山阳县| 昌吉市| 丰城市| 南江县| 安义县| 鹰潭市| 奉节县| 上蔡县| 孟州市| 明水县| 长宁县| 阳信县| 宁陕县| 安多县| 阿拉善左旗| 富源县| 壶关县| 梅河口市| 辽阳市| 巨野县| 古田县| 甘南县| 桃园县| 兴业县| 凯里市| 连城县|