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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 17:35:43