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

Unity3D脚本协程执行顺序异常问题排查与修复求助

问题

我编写了两个Unity脚本:WebSourceConfig用于读取JSON配置文件,WebSourceLink用于在应用中渲染按钮。要求WebSourceLink必须在WebSourceConfig完成配置读取后再执行,否则应用会无数据可解析。我尝试用协程控制执行顺序,但发现无论配置文件是否完成下载,configSource.webConfig始终不为null,导致协程没等配置读取完成就直接执行了ReadData。

WebSourceLink脚本代码

public class WebSourceLink : MonoBehaviour
{
    public WebSourceConfig configSource;

    //Buttons references
    public Button buttonPrefab;
    public GameObject Buttons;

    //Browser windows references
    public GameObject browserWindow;
    public GameObject Windows;

    private Vector2 _nextWindowPosition = new(-900f, 0f);

    void Start()
    {
        // Start coroutne to wait for read config file
        StartCoroutine(WaitForConfigRead());
    }

    IEnumerator WaitForConfigRead()
    {
        // Continually check if the config file has been read
        while (configSource.webConfig == null)
        {
            // Use the yield return statement to pause the coroutine
            // until the next frame
            Debug.Log("Null data parsed");
            yield return null;
        }

        Debug.Log(configSource.webConfig);

        // Once the file is read by WebSourceConfig, read the config data
        ReadData();

        InvokeRepeating("ReadData", 300, 5 * 60);
    }

    // 省略其他方法...
}

WebSourceConfig脚本代码

public class WebSourceConfig : MonoBehaviour
{
    public WebConfiguration webConfig;

    // Store the last time the config file was read
    private DateTime lastReadTime = DateTime.MinValue;

    void Start()
    {
        // Start the coroutine to wait for the file to be downloaded
        StartCoroutine(WaitForConfigFile());
    }

    IEnumerator WaitForConfigFile()
    {
        // Continually check if the config file has been downloaded yet
        while (!File.Exists(Path.Combine(Application.streamingAssetsPath, "config.json")))
        {
            // Use the yield return statement to pause the coroutine
            // until the next frame
            yield return null;
        }

        // Once the file is downloaded, read the config file
        ReadConfigFile();

        // Call the CheckConfigFile method every 5 minutes
        InvokeRepeating("CheckConfigFile", 300, 5 * 60);
    }

    void CheckConfigFile()
    {
        // Check for changes to the config file
        // If the file has changed, read it again
        if (HasConfigFileChanged())
        {
            ReadConfigFile();
        }
    }

    void ReadConfigFile()
    {
        try
        {
            // Use the Path.Combine method to create the full path to the JSON file
            string jsonPath = Path.Combine(Application.streamingAssetsPath, "config.json");

            // Read the JSON file into a string
            string json = File.ReadAllText(jsonPath);

            // Convert the JSON string into a strongly-typed object
            webConfig = JsonUtility.FromJson<WebConfiguration>(json);
        }
        catch (System.Exception ex)
        {
            // Print the error message to the console
            Debug.LogError(ex.Message);
        }
    }

    bool HasConfigFileChanged()
    {
        // Get the last modified time of the file
        string jsonPath = Path.Combine(Application.streamingAssetsPath, "config.json");
        var lastModifiedTime = File.GetLastWriteTime(jsonPath);

        // Check if the last modified time is different from the time when we last read the file
        if (lastModifiedTime != lastReadTime)
        {
            // Update the lastReadTime to the current time
            lastReadTime = lastModifiedTime;

            // Return true to indicate that the file has changed
            return true;
        }

        // Return false if the file has not changed
        return false;
    }
}
问题分析
  1. Inspector手动赋值:如果在Unity编辑器中,WebSourceConfig的webConfig字段被手动赋值(比如拖入了WebConfiguration实例),那么webConfig初始状态就不为null,协程的等待条件直接失效。
  2. JsonUtility解析特性:JsonUtility.FromJson即使解析失败(JSON格式错误、字段不匹配),也不会返回null,而是返回一个所有字段为默认值的空WebConfiguration实例,这会让webConfig != null的判断成立,导致协程提前结束。
  3. 文件存在不代表有效:WebSourceConfig的协程只检查文件是否存在,但文件存在不代表内容可解析,解析失败时webConfig依然会被赋值为空实例,同样触发WebSourceLink的协程结束条件。
修复方案

方案1:新增加载状态标记(推荐)

在WebSourceConfig中添加明确的加载状态标记,替代webConfig == null的判断逻辑,这是最可靠的方式。

修改WebSourceConfig

public class WebSourceConfig : MonoBehaviour
{
    [HideInInspector] // 防止Inspector手动赋值
    public WebConfiguration webConfig;
    // 新增:标记配置是否成功加载完成
    public bool IsConfigLoaded { get; private set; }

    private DateTime lastReadTime = DateTime.MinValue;

    void Start()
    {
        StartCoroutine(WaitForConfigFile());
    }

    IEnumerator WaitForConfigFile()
    {
        string jsonPath = Path.Combine(Application.streamingAssetsPath, "config.json");
        while (!File.Exists(jsonPath))
        {
            yield return null;
        }
        // 读取后更新加载状态
        IsConfigLoaded = ReadConfigFile();
        InvokeRepeating("CheckConfigFile", 300, 5 * 60);
    }

    void CheckConfigFile()
    {
        if (HasConfigFileChanged())
        {
            // 更新加载状态
            IsConfigLoaded = ReadConfigFile();
        }
    }

    // 修改为返回bool,标识读取是否成功
    bool ReadConfigFile()
    {
        try
        {
            string jsonPath = Path.Combine(Application.streamingAssetsPath, "config.json");
            string json = File.ReadAllText(jsonPath);
            var tempConfig = JsonUtility.FromJson<WebConfiguration>(json);
            
            // 验证配置有效性(根据你的WebConfiguration结构调整,比如检查关键字段)
            if (tempConfig != null && !string.IsNullOrEmpty(tempConfig.someKeyField))
            {
                webConfig = tempConfig;
                return true;
            }
            else
            {
                Debug.LogError("配置文件内容无效,关键字段缺失");
                webConfig = null;
                return false;
            }
        }
        catch (System.Exception ex)
        {
            Debug.LogError($"读取配置失败:{ex.Message}");
            webConfig = null;
            return false;
        }
    }

    // 保留原有HasConfigFileChanged方法
    bool HasConfigFileChanged()
    {
        string jsonPath = Path.Combine(Application.streamingAssetsPath, "config.json");
        var lastModifiedTime = File.GetLastWriteTime(jsonPath);
        if (lastModifiedTime != lastReadTime)
        {
            lastReadTime = lastModifiedTime;
            return true;
        }
        return false;
    }
}

修改WebSourceLink的协程

IEnumerator WaitForConfigRead()
{
    // 等待configSource存在且配置加载完成
    while (configSource == null || !configSource.IsConfigLoaded)
    {
        Debug.Log("等待配置加载...");
        yield return null;
    }

    Debug.Log("配置加载完成,开始渲染按钮");
    ReadData();
    InvokeRepeating("ReadData", 300, 5 * 60);
}

方案2:修复webConfig的null判断逻辑

如果不想新增标记,可以修改ReadConfigFile方法,在解析失败或内容无效时将webConfig设为null,同时确保Inspector中webConfig字段留空。

void ReadConfigFile()
{
    try
    {
        string jsonPath = Path.Combine(Application.streamingAssetsPath, "config.json");
        string json = File.ReadAllText(jsonPath);
        var tempConfig = JsonUtility.FromJson<WebConfiguration>(json);
        
        // 验证配置有效性,无效则设为null
        if (tempConfig != null && !string.IsNullOrEmpty(tempConfig.someKeyField))
        {
            webConfig = tempConfig;
        }
        else
        {
            webConfig = null;
            Debug.LogError("配置内容无效");
        }
    }
    catch (System.Exception ex)
    {
        Debug.LogError($"读取配置失败:{ex.Message}");
        webConfig = null; // 异常时强制设为null
    }
}

同时给webConfig字段添加[HideInInspector],防止手动赋值:

[HideInInspector]
public WebConfiguration webConfig;

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 07:30:48