Unity3D发送API请求时游戏卡顿,如何实现后台无阻塞请求?
解决Unity主线程因API请求阻塞的问题
核心问题分析
你当前的代码卡顿原因有两个:
- 直接传递原
frames列表给CommitFrames,随后立刻Clear(),会导致请求发送空数据;同时序列化120帧的Base64字符串到JSON时,耗时操作在主线程执行,阻塞帧更新。 - UnityWebRequest的
SendWebRequest本身是异步协程,但**上传数据的准备(JSON序列化、字节转换)**在主线程完成,大体积数据会占用主线程时间片。
解决方案步骤
1. 避免数据竞争,传递帧数据副本
不要直接操作原frames列表,提交时创建副本,防止主线程清空数据导致请求数据丢失。
2. 把耗时的序列化操作移到后台线程
利用Task.Run在后台线程完成JSON序列化和字节转换,避免占用主线程。UnityWebRequest必须在主线程创建和发送,所以后台处理完数据后回到主线程执行请求。
3. 优化帧存储格式(可选但推荐)
放弃Base64编码,直接存储byte[]格式的图片数据,减少33%的数据体积,同时避免Base64编码的耗时。
修改后的完整代码
using System; using System.Collections; using System.Collections.Generic; using System.Text; using UnityEngine; using UnityEngine.Networking; using System.Threading.Tasks; public class Recorder : MonoBehaviour { private float captureInterval = 1f / 24f; // 改为存储byte[],避免Base64编码的额外开销 private readonly List<byte[]> frames = new(); void Update() { if (Time.timeSinceLevelLoad >= captureInterval) { StartCoroutine(CaptureFrame()); captureInterval = Time.timeSinceLevelLoad + (1f / 24f); if (frames.Count >= 120) { // 创建帧数据副本,避免主线程清空影响请求 var framesCopy = new List<byte[]>(frames); frames.Clear(); // 启动后台处理+主线程请求的流程 StartCoroutine(CommitFramesAsync(framesCopy)); } } } IEnumerator CaptureFrame() { yield return new WaitForEndOfFrame(); Texture2D texture = ScreenCapture.CaptureScreenshotAsTexture(); byte[] jpg = texture.EncodeToJPG(); // 直接存byte[],跳过Base64编码 frames.Add(jpg); Destroy(texture); } IEnumerator CommitFramesAsync(List<byte[]> framesData) { byte[] uploadBytes = null; // 在后台线程完成JSON序列化(JsonUtility可在后台使用,只要操作非Unity引擎对象) await Task.Run(() => { var payload = new CommitFramesData { frames = framesData }; string json = JsonUtility.ToJson(payload); uploadBytes = Encoding.UTF8.GetBytes(json); }); // 回到主线程发送UnityWebRequest var www = CreateRequest("http://my-api.test/api/frames", RequestType.POST, uploadBytes); yield return www.SendWebRequest(); // 处理请求结果 if (www.result != UnityWebRequest.Result.Success) { Debug.LogError($"请求失败: {www.error}"); } www.Dispose(); } // 修改CreateRequest,直接接收预先生成的字节数组 private UnityWebRequest CreateRequest(string path, RequestType type = RequestType.GET, byte[] uploadData = null) { var request = new UnityWebRequest(path, type.ToString()); if (uploadData != null) { request.uploadHandler = new UploadHandlerRaw(uploadData); } request.downloadHandler = new DownloadHandlerBuffer(); request.SetRequestHeader("Content-Type", "application/json"); return request; } } public enum RequestType { GET = 0, POST = 1, PUT = 2 } [Serializable] class CommitFramesData { // 对应byte[]列表,JsonUtility会自动序列化 public List<byte[]> frames; }
关键改动说明
- 帧存储优化:用
byte[]替代Base64字符串,减少内存占用和编码耗时,同时降低上传数据量。 - 数据副本传递:提交时创建
frames的副本,避免主线程清空导致请求数据丢失。 - 后台序列化:用
Task.Run把JSON序列化和字节转换放到后台线程执行,主线程只负责启动任务和等待结果,不被阻塞。 - 请求结果处理:增加了请求失败的日志,方便调试。
注意事项
- Unity的
JsonUtility可以在后台线程使用,但不能序列化Unity引擎对象(如Texture2D、GameObject等),这里序列化的是普通List<byte[]>,无问题。 - 如果API要求必须接收Base64格式,需在后台线程完成Base64编码,不要放在主线程。
- 若需更复杂的后台线程管理,可使用Unity的
UnityMainThreadDispatcher工具类,但上述代码已足够解决卡顿问题。
内容的提问来源于stack exchange,提问作者Adil Rakhimbayev
相关产品推荐
相关产品推荐

