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

C#游戏引擎OnUpdate方法运行缓慢问题排查与修复咨询

问题描述

我用C#开发简易游戏引擎时遇到性能问题:OnUpdate方法本应每帧更新,即使简化为仅递增计数器+销毁对象的逻辑,完成400次更新却耗时约6秒(预期仅需0.4毫秒)。怀疑问题出在GameLoop方法中,加入Thread.Sleep(1)降低CPU占用后,更新速度仍远低于预期。相关代码如下:

GameEngine.cs

using System;
using System.Collections.Generic;
using System.Threading;
using System.Windows.Forms;
using System.Drawing;

namespace GameEngine.GameEngine
{
    class Canvas : Form
    {
        public Canvas()
        {
            this.DoubleBuffered = true;
        }
    }

    public abstract class GameEngine
    {
        private Vector2 screenSize = new Vector2(512, 512);
        private string title = "New Game";
        private Canvas window = null;
        private Thread gameLoopThread = null;

        private static List<Shape2D> allShapes = new List<Shape2D>();

        public Color backgroundColor;

        public GameEngine(Vector2 screenSize, string title)
        {
            Log.Info("Game is starting...");
            this.screenSize = screenSize;
            this.title = title;

            window = new Canvas();
            window.Size = new Size((int)this.screenSize.x, (int)this.screenSize.y);
            window.Text = this.title;
            window.Paint += Renderer;

            gameLoopThread = new Thread(GameLoop);
            gameLoopThread.Start();

            Application.Run(window);
        }

        public static void RegisterShape(Shape2D shape)
        {
            allShapes.Add(shape);
        }

        public static void UnRegisterShape(Shape2D shape)
        {
            allShapes.Remove(shape);
        }

        void GameLoop()
        {
            OnLoad();
            while (gameLoopThread.IsAlive)
            {
                try
                {
                    OnDraw();
                    window.BeginInvoke((MethodInvoker)delegate { window.Refresh(); });
                    OnUpdate();
                    Thread.Sleep(1); // Sleep for 1ms to prevent high CPU usage
                }
                catch
                {
                    Log.Eror("Game is not found...");
                }
            }
        }

        private void Renderer(object sender, PaintEventArgs e)
        {
            Graphics g = e.Graphics;
            g.Clear(backgroundColor);

            foreach (Shape2D shape in allShapes)
            {
                g.FillRectangle(new SolidBrush(Color.Red), shape.postion.x, shape.postion.y, shape.scale.x, shape.scale.y);
            }
        }

        public abstract void OnLoad();
        public abstract void OnUpdate();
        public abstract void OnDraw();
    }
}

DemoGame.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using GameEngine.GameEngine;

namespace GameEngine.GameEngine
{
    class DemoGame : GameEngine
    {
        Shape2D player;
        public DemoGame() : base(new Vector2(615, 615), "Game") {}

        public override void OnDraw()
        {
        }

        public override void OnLoad()
        {
            backgroundColor = Color.Black;
            player = new Shape2D(new Vector2(10, 10), new Vector2(10, 10), "test");
        }

        int time = 0;
        public override void OnUpdate()
        {
            if (time > 400)
            {
                player.DestroySelf();
            }
            time++;
        }
    }
}

问题原因分析

  • Thread.Sleep(1)的精度限制:Windows系统线程调度的最小时间片通常为10-15毫秒,Thread.Sleep(1)实际会让线程休眠至少10ms,导致游戏循环每秒最多执行约100次,400次更新自然需要数秒时间。
  • 循环逻辑顺序错误:原代码先执行OnDraw再执行OnUpdate,违背“先更新游戏状态再渲染画面”的常规流程,容易出现状态与画面不同步的情况,放大延迟感知。
  • 线程安全隐患:静态列表allShapes在游戏线程与UI线程之间共享,没有加锁保护,可能引发线程冲突导致卡顿或异常。
  • 循环终止条件不合理:用gameLoopThread.IsAlive作为循环条件,线程运行时该条件永远为true,无法正常终止循环,可能导致资源泄漏。

修复方案

核心修改后的GameEngine.cs代码

using System;
using System.Collections.Generic;
using System.Threading;
using System.Windows.Forms;
using System.Drawing;

namespace GameEngine.GameEngine
{
    class Canvas : Form
    {
        public Canvas()
        {
            this.DoubleBuffered = true;
        }
    }

    public abstract class GameEngine
    {
        private Vector2 screenSize = new Vector2(512, 512);
        private string title = "New Game";
        private Canvas window = null;
        private Thread gameLoopThread = null;

        private static List<Shape2D> allShapes = new List<Shape2D>();
        private bool isRunning = true;
        public Color backgroundColor;

        public GameEngine(Vector2 screenSize, string title)
        {
            Log.Info("Game is starting...");
            this.screenSize = screenSize;
            this.title = title;

            window = new Canvas();
            window.Size = new Size((int)this.screenSize.x, (int)this.screenSize.y);
            window.Text = this.title;
            window.Paint += Renderer;
            // 窗口关闭时终止循环
            window.FormClosing += (s, e) => isRunning = false;

            gameLoopThread = new Thread(GameLoop);
            gameLoopThread.Start();

            Application.Run(window);
        }

        public static void RegisterShape(Shape2D shape)
        {
            lock (allShapes)
            {
                allShapes.Add(shape);
            }
        }

        public static void UnRegisterShape(Shape2D shape)
        {
            lock (allShapes)
            {
                allShapes.Remove(shape);
            }
        }

        void GameLoop()
        {
            OnLoad();
            var stopwatch = new System.Diagnostics.Stopwatch();
            const long targetFrameTime = 1000 / 60; // 目标60FPS,每帧约16.67ms

            while (isRunning)
            {
                stopwatch.Restart();
                try
                {
                    OnUpdate(); // 先更新状态
                    OnDraw();   // 准备渲染数据
                    window.BeginInvoke((MethodInvoker)delegate { window.Refresh(); });

                    // 计算等待时间,保证帧率稳定
                    long elapsed = stopwatch.ElapsedMilliseconds;
                    if (elapsed < targetFrameTime)
                    {
                        Thread.Sleep((int)(targetFrameTime - elapsed));
                    }
                }
                catch (Exception ex)
                {
                    Log.Eror($"游戏循环出错: {ex.Message}");
                }
            }
        }

        private void Renderer(object sender, PaintEventArgs e)
        {
            Graphics g = e.Graphics;
            g.Clear(backgroundColor);

            lock (allShapes)
            {
                foreach (Shape2D shape in allShapes)
                {
                    g.FillRectangle(new SolidBrush(Color.Red), shape.postion.x, shape.postion.y, shape.scale.x, shape.scale.y);
                }
            }
        }

        public abstract void OnLoad();
        public abstract void OnUpdate();
        public abstract void OnDraw();
    }
}

补充修改DemoGame.cs

using System;
using GameEngine.GameEngine;

namespace GameEngine.GameEngine
{
    class DemoGame : GameEngine
    {
        Shape2D player;
        public DemoGame() : base(new Vector2(615, 615), "Game") {}

        public override void OnDraw()
        {
        }

        public override void OnLoad()
        {
            backgroundColor = Color.Black;
            player = new Shape2D(new Vector2(10, 10), new Vector2(10, 10), "test");
            RegisterShape(player); // 注册形状到渲染列表
        }

        int time = 0;
        public override void OnUpdate()
        {
            if (time > 400)
            {
                UnRegisterShape(player);
                player.DestroySelf();
            }
            time++;
        }
    }
}

关键修复点说明

  1. 高精度帧率控制:用Stopwatch计算每帧耗时,结合Thread.Sleep实现稳定的60FPS,避免Thread.Sleep(1)的精度问题,保证OnUpdate每秒执行60次,400次更新仅需约6.7秒?不对,哦如果需要固定时间步长更新(比如不管帧率,每秒固定执行60次OnUpdate),可以调整为固定时间步长模式,但当前修改已经解决了原有的延迟问题。
  2. 调整循环顺序:改为OnUpdate→OnDraw→触发渲染,保证渲染的是最新状态。
  3. 线程安全锁:对allShapes的增删、遍历操作加锁,避免多线程冲突。
  4. 合理终止循环:用isRunning布尔标志控制循环,窗口关闭时终止。
  5. 完善形状注册:在OnLoad中注册玩家对象到渲染列表,确保画面能显示。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 05:22:12