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

如何在C#中实现不阻塞主线程的类Unity Update定时循环

实现非阻塞的Unity-style Update循环(指定帧率)

你的现有代码有两个关键问题:一是Task.Delay(tickrate / 60)没有用await,导致延迟完全不生效,循环会全速执行;二是整个方法是同步的,会一直占用调用线程,无法做到非阻塞。

下面是几种可行的解决方案:


基础非阻塞实现

把方法改成异步模式,通过async/await让线程在延迟期间被释放,去处理其他任务:

// 注意方法签名改为async Task,表明这是异步方法
public async Task UpdateAsync(int tickrate)
{
    // 计算每帧的间隔毫秒数:tickrate为每秒需要执行的次数
    int frameIntervalMs = 1000 / tickrate;
    
    while (true)
    {
        // 执行你的更新逻辑
        foreach (IAgent agent in Agents)
        {
            agent.Move();
        }

        // await会释放当前线程,到时间后再回到循环
        await Task.Delay(frameIntervalMs);
    }
}

调用时直接启动即可,不会阻塞调用线程:

// 在异步上下文外可以用下划线忽略返回的Task,或者用await(如果在异步方法内)
_ = UpdateAsync(60);

高精度帧率控制方案

Task.Delay的精度有限(通常在10-15ms左右),如果需要更精准的帧率,可以用Stopwatch计算每帧的实际耗时,动态调整延迟时间:

public async Task UpdateAsync(int tickrate)
{
    int targetFrameIntervalMs = 1000 / tickrate;
    var stopwatch = Stopwatch.StartNew();
    long lastFrameEndTime = 0;

    while (true)
    {
        // 执行更新逻辑
        foreach (IAgent agent in Agents)
        {
            agent.Move();
        }

        // 计算当前帧耗时,调整延迟
        long currentTime = stopwatch.ElapsedMilliseconds;
        long elapsedSinceLastFrame = currentTime - lastFrameEndTime;
        long delayMs = targetFrameIntervalMs - elapsedSinceLastFrame;

        // 避免延迟为负数,确保线程能让出
        if (delayMs > 0)
        {
            await Task.Delay((int)delayMs);
        }
        else
        {
            await Task.Yield(); // 强制让出线程,避免完全占用CPU
        }

        lastFrameEndTime = stopwatch.ElapsedMilliseconds;
    }
}

支持中途停止的版本

如果需要在运行时终止循环,不要用while(true),改用CancellationToken实现可取消的循环:

public async Task UpdateAsync(int tickrate, CancellationToken cancellationToken)
{
    int targetFrameIntervalMs = 1000 / tickrate;
    var stopwatch = Stopwatch.StartNew();
    long lastFrameEndTime = 0;

    // 检查取消令牌,判断是否继续循环
    while (!cancellationToken.IsCancellationRequested)
    {
        foreach (IAgent agent in Agents)
        {
            agent.Move();
        }

        long currentTime = stopwatch.ElapsedMilliseconds;
        long elapsedSinceLastFrame = currentTime - lastFrameEndTime;
        long delayMs = targetFrameIntervalMs - elapsedSinceLastFrame;

        if (delayMs > 0)
        {
            // 传递取消令牌,支持中途终止延迟
            await Task.Delay((int)delayMs, cancellationToken);
        }
        else
        {
            await Task.Yield();
        }

        lastFrameEndTime = stopwatch.ElapsedMilliseconds;
    }
}

调用示例:

var cts = new CancellationTokenSource();
// 启动循环
_ = UpdateAsync(60, cts.Token);

// 需要停止时调用
cts.Cancel();

额外注意点

  • 你原来代码里的tickrate / 60逻辑有问题:如果tickrate是每秒执行的次数,正确的间隔计算应该是1000 / tickrate(毫秒)。
  • 异步方法不要用async void,除非是事件处理程序,否则用async Task方便后续处理。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 12:40:24