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

C# Unity3D中Firebase数据遍历foreach循环仅执行一次问题

问题根因

核心原因是Unity所有继承自UnityEngine.Object的API(包括Instantiate、GameObject操作、组件属性赋值等)严格限制只能在主线程调用,你代码里用的默认ContinueWith回调会在ThreadPool后台线程执行,触发跨线程访问异常后直接终止了整个任务逻辑,和你观察到的现象完全吻合:

  • snapshot.ChildrenCount能正确读到值4:这个属性是Firebase SDK拉取解析数据后自带的元数据,不依赖Unity主线程,后台线程读取完全正常
  • 第一次循环中Instantiate调用前的Debug.Log能正常输出:这部分代码还没触发Unity主线程访问限制,多数Unity版本允许后台线程调用Debug.Log输出文本,所以日志可以正常打印
  • Instantiate执行后无日志、剩余3次循环完全不运行:调用Instantiate时会直接抛出UnityException: Internal_CreateGameObject is not allowed to be called from a background thread异常,整个Task因未捕获的异常直接终止,后续所有代码不会再执行。
修复方案

方案1:使用Firebase自带的主线程回调扩展(推荐)

Firebase for Unity官方提供了ContinueWithOnMainThread扩展方法,会自动把回调逻辑调度到Unity主线程执行,完全满足Unity API的线程要求,只需要两步:

  1. 代码里引入命名空间:using Firebase.Extensions;
  2. 把原来的ContinueWith替换成ContinueWithOnMainThread

修正后的核心代码示例:

using Firebase;
using Firebase.Database;
using Firebase.Extensions;
using UnityEngine;

public class TreeLoader : MonoBehaviour
{
    public GameObject treePrefab;
    private DatabaseReference reference;
    private string scenarioName;

    void GetTrees()
    {
        reference.Child("Player").Child(scenarioName).GetValueAsync().ContinueWithOnMainThread(task =>
        {
            if (task.IsFaulted || task.IsCanceled)
            {
                Debug.LogError($"拉取树木数据失败: {task.Exception}");
                return;
            }

            DataSnapshot snapshot = task.Result;
            Debug.Log($"共加载到{snapshot.ChildrenCount}个树木节点");
            
            // 遍历节点生成树木
            foreach (DataSnapshot treeNode in snapshot.Children)
            {
                // 读取坐标、旋转参数,做类型校验避免脏数据中断逻辑
                if (!treeNode.HasChild("posX") || !treeNode.HasChild("posY") || !treeNode.HasChild("posZ")
                    || !treeNode.HasChild("rotX") || !treeNode.HasChild("rotY") || !treeNode.HasChild("rotZ") || !treeNode.HasChild("rotW"))
                {
                    Debug.LogWarning($"跳过无效树木节点: {treeNode.Key},参数不全");
                    continue;
                }

                Vector3 pos = new Vector3(
                    float.Parse(treeNode.Child("posX").Value.ToString()),
                    float.Parse(treeNode.Child("posY").Value.ToString()),
                    float.Parse(treeNode.Child("posZ").Value.ToString())
                );
                Quaternion rot = new Quaternion(
                    float.Parse(treeNode.Child("rotX").Value.ToString()),
                    float.Parse(treeNode.Child("rotY").Value.ToString()),
                    float.Parse(treeNode.Child("rotZ").Value.ToString()),
                    float.Parse(treeNode.Child("rotW").Value.ToString())
                );
                
                Debug.Log($"准备生成树木,坐标:{pos} 旋转:{rot}");
                // 主线程调用Instantiate不会抛异常
                Instantiate(treePrefab, pos, rot);
                Debug.Log($"树木{treeNode.Key}生成完成");
            }
        });
    }
}

方案2:使用async/await语法(更简洁)

Unity 2018及以上版本支持async/await,Firebase的Task可以直接await,await后默认会回到Unity主线程,代码可读性更高:

async void GetTrees()
{
    try
    {
        DataSnapshot snapshot = await reference.Child("Player").Child(scenarioName).GetValueAsync();
        Debug.Log($"共加载到{snapshot.ChildrenCount}个树木节点");
        // 遍历、Instantiate逻辑和上方示例一致,所有代码都在主线程执行
        foreach (DataSnapshot treeNode in snapshot.Children)
        {
            // 省略参数校验、读取坐标旋转、Instantiate逻辑,和上方示例相同
        }
    }
    catch (System.Exception e)
    {
        Debug.LogError($"加载树木失败: {e}");
    }
}

额外注意事项

  • 不要在后台线程调用任何操作UnityEngine.Object的API,包括Instantiate、Transform赋值、SetActive等,否则必然抛线程访问异常
  • 遍历数据时一定要加参数合法性校验,避免单个节点数据异常导致整个生成逻辑中断
  • 处理Firebase Task时先判断IsFaulted/IsCanceled状态,不要直接取Result,避免网络错误、权限错误导致未捕获异常

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.11 16:16:24