You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在C#中精准限制FPS?现有实现帧率不达目标求修正

问题

开发一款持续轮询视频和游戏窗口、捕获屏幕并处理的应用,需要实现可配置的FPS限制以减少资源占用。使用Stopwatch计时Ticks实现帧率控制循环,但目标60FPS时实际仅能达到35-40,且实际与目标帧率比例固定。检查数学逻辑无结果,断点调试会干扰计时,求排查及修复方案。

public class ProcessLoop
{
    const int TARGET_FPS = 60;
    const long OPTIMAL_TIME = (TimeSpan.TicksPerSecond / TARGET_FPS);

    private static Stopwatch _time = new Stopwatch();
    private int _frames = 0;
    private int _fps = 0;

    public void Run(CancellationToken stoppingToken, Action process)
    {
        _time.Start();
        long lastLoopTime = _time.ElapsedTicks;
        long lastSecondUpdate = lastLoopTime;

        while (!stoppingToken.IsCancellationRequested)
        {
            lastLoopTime = _time.ElapsedTicks;

            // Process one frame here
            process();
            _frames++;

            // The time after processing work
            var now = _time.ElapsedTicks;

            // How long it took to process the work done this frame
            var deltatime = now - lastLoopTime;

            if (now - lastSecondUpdate >= TimeSpan.TicksPerSecond)
            {
                _fps = _frames;
                _frames = 0;
                lastSecondUpdate = 0;
                _time.Restart();
                Console.WriteLine($"FPS: {_fps}");
            }

            // Sleep to approach optimal time per frame
            if (deltatime < OPTIMAL_TIME)
            {
                Thread.Sleep(TimeSpan.FromTicks(OPTIMAL_TIME - deltatime));
            }
        }
    }
}
排查与修复方案

核心问题

  1. Thread.Sleep精度不足:Windows系统中Thread.Sleep的最小精度通常为10-15ms,而60FPS单帧理想时间仅约16.67ms。当计算出的休眠时间小于系统最小睡眠精度时,实际休眠会被向上取整,导致单帧总时间远超预期,直接拉低帧率。
  2. FPS统计逻辑混乱:每次统计FPS时调用_time.Restart()并重置lastSecondUpdate = 0,破坏了计时基准的连续性,不仅导致FPS统计不准,还干扰了帧率控制的时间计算。

修复步骤

1. 替换低精度休眠为高精度等待

用SpinWait结合Stopwatch实现短时间高精度等待,避免系统睡眠的精度损失:

// 替换原有的Thread.Sleep代码块
if (deltatime < OPTIMAL_TIME)
{
    long targetTick = now + (OPTIMAL_TIME - deltatime);
    while (_time.ElapsedTicks < targetTick)
    {
        SpinWait.SpinOnce(); // 低CPU消耗的自旋等待,适合短时间等待场景
    }
}

若需要平衡CPU占用和精度,可采用混合方案:

if (deltatime < OPTIMAL_TIME)
{
    long remainingTicks = OPTIMAL_TIME - deltatime;
    // 先休眠到接近目标时间,剩余少量时间用自旋
    if (remainingTicks > TimeSpan.TicksPerMillisecond * 2)
    {
        Thread.Sleep(TimeSpan.FromTicks(remainingTicks - TimeSpan.TicksPerMillisecond * 2));
    }
    // 最后阶段自旋等待保证精度
    long targetTick = now + OPTIMAL_TIME - deltatime;
    while (_time.ElapsedTicks < targetTick)
    {
        SpinWait.SpinOnce();
    }
}

2. 修复FPS统计逻辑

移除_time.Restart(),保持计时基准连续:

if (now - lastSecondUpdate >= TimeSpan.TicksPerSecond)
{
    _fps = _frames;
    _frames = 0;
    lastSecondUpdate = now; // 用当前时间更新统计基准,而非重置为0
    Console.WriteLine($"FPS: {_fps}");
}

3. 额外优化建议

  • 将_time改为实例字段:静态字段会导致多个ProcessLoop实例共享计时器,引发计时混乱。
  • 支持动态配置FPS:把TARGET_FPS改为构造函数参数,满足可配置需求。
  • 处理超长帧:若process()执行时间超过单帧理想时间,直接跳过休眠,避免延迟累积。

内容的提问来源于stack exchange,提问作者FranklyBrandon

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 16:05:03