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

