为什么在回调中导致代码停止?

问题描述

使用GetValueAsync()。ContinueWith(task ..)中的回调通过Firebase恢复数据后,我想实例化预制件,以查看排行榜的得分列表。但是,它什么也不做,我没有错误。一旦遇到“ this”或“ instantiate”,代码便会立即停在回调UseSores中。

public class leaderboardmanager : MonoBehavIoUr
{

    public GameObject rowLeardBoard;
    FirebaseDB_Read read;
    float positionX; 
    int nbrows = 10;

    void Start()
    {   
        read = (gameObject.AddComponent<FirebaseDB_Read>());
        GetscorePlayer();
    } 

    void GetscorePlayer()
    {
        read.Getscores(Usescores,"entries/leaderBoard/",nbrows);
    }

    void Usescores(IList<FirebaseDB_Read.score> scores)
    {
        Debug.Log("arrive here");
        positionX = this.transform.position.y; 
        Debug.Log("does not arrive here");
    }
}

这里是要获取我的数据:

public class FirebaseDB_Read : MonoBehavIoUr
{

    public class score
    {
        public string UID;
        public string score;
        public int rank;
    }


    public void Getscores(Action<IList<score>> callback,string URL_TO_scoreS,int limit)
    {
        DatabaseReference scoresRef = FirebaseDatabase.DefaultInstance.GetReference(URL_TO_scoreS);

        scoresRef.OrderByChild("score").LimitToLast(limit).GetValueAsync().ContinueWith(task =>
        {
            DataSnapshot snapshot = task.Result;
            IList<score> objectsList = new List<score> { };

            int i = 1;
            foreach (var childSnapshot in snapshot.Children)
            {
                score score = new score();
                score.rank = i;
                score.UID = childSnapshot.Child("UID").GetValue(true).ToString();
                score.score = childSnapshot.Child("score").GetValue(true).ToString();

                objectsList.Add(score);
                i++;
            }

            callback(objectsList);
        });
    }
}

解决方法

这是Unity中经常问到的问题:因为您ContinueWith后台线程上!

Unity不是线程安全的,这意味着大多数Unity API只能在Unity主线程中使用。

Firebase提供了一个专门针对Unity的扩展:ContinueWithOnMainThread,可确保在访问API有效的Unity主线程中处理结果。

scoresRef.OrderByChild("score").LimitToLast(limit).GetValueAsync().ContinueWithOnMainThread(task =>
{
    ...
});

作为替代,您可以使用一种所谓的“主线程分配器”模式,并确保callback在接收方的主线程中执行。这样的好处是列表上仍然昂贵的操作全部在后台线程上执行,而不会影响UI性能

scoresRef.OrderByChild("score").LimitToLast(limit).GetValueAsync().ContinueWith(task =>
{
    ...
});

但是在FirebaseDB_Read

的接收方
private readonly ConcurrentQueue<Action> _mainThreadActions = new ConcurrentQueue<Action>();

private void Update()
{
    if(_mainThreadAction.Count > 0)
    {
        while(_mainThreadActions.TryDequeue(out var action))
        {
            action?.Invoke();
        }
    }
}

void GetScorePlayer()
{
    read.GetScores(UseScores,"entries/LeaderBoard/",nbRows);
}

void UseScores(IList<FirebaseDB_Read.Score> scores)
{
    // handle this in the next main thread update
    _mainThreadActions.Enqueue(() =>
    {
        Debug.Log("arrive here");
        positionX = this.transform.position.y; 
        Debug.Log("does not arrive here");
    }
}

当然,这反过来会增加一些开销,用于检查Update中的任何新动作。因此,如果您计划使用多个此类后台操作,请确保在一个中央位置实施这些操作,以保持有限的开销;)