Таким образом, мой ответ очень похож на принятый ответ от Милода , но немного по-другому, так как мне потребовалось некоторое время, чтобы обернуть мою голову вокруг него, хотя он все ещеработает.
Проблема: Обычно весь ваш код выполняется в Unity с одним потоком, поскольку Unity является однопоточным, однако при работе с такими API, как Firebase, для которых требуются обратные вызовы, функции обратного вызова будут обрабатываться новым потоком.Это может привести к гоночным условиям, особенно на однопоточном двигателе, таком как Unity.
Решение (от Unity): Начиная с Unity 2017.X,Unity теперь требует изменения компонентов пользовательского интерфейса для запуска в главном потоке (то есть в первом потоке, который был запущен с Unity).
Что влияет?: Преимущественновызовы, которые изменяют пользовательский интерфейс, например ...
gameObject.SetActive(true); // (or false)
textObject.Text = "some string" // (from UnityEngine.UI)
Как это относится к вашему коду:
public void SignInWithEmail() {
// auth.SignInWithEmailAndPasswordAsyn() is run on the local thread,
// ...so no issues here
auth.SignInWithEmailAndPasswordAsync(email, password).ContinueWith(task => {
// .ContinueWith() is an asynchronous call
// ...to the lambda function defined within the task=> { }
// and most importantly, it will be run on a different thread, hence the issue
DatabaseReference.GetValueAsync().ContinueWith(task => {
//HERE IS THE PROBLEM
userPanel.SetActive(true);
authPanel.SetActive(false);
}
}
}
Предлагаемое решение: Для тех вызовов, для которых требуются функции обратного вызова, например ...
DatabaseReference.GetValueAsync()
... вы можете ...
- отправить их функции, которая настроена для запуска в этом начальном потоке.
- ... и которая использует очередь, чтобы гарантировать, что они будут выполняться в том порядке, в которомони были добавлены.
- ... и с использованием шаблона синглтона, в соответствии с рекомендациями команды Unity.
Фактическое решение
- Поместитекод ниже в вашей сцене на gameObject, который всегда будет включен, так что у вас есть рабочий, который ...
- всегда работает в локальном потоке
- можно отправить эти функции обратного вызова, чтобызапустить в локальном потоке.
using System;
using System.Collections.Generic;
using UnityEngine;
internal class UnityMainThread : MonoBehaviour
{
internal static UnityMainThread wkr;
Queue<Action> jobs = new Queue<Action>();
void Awake() {
wkr = this;
}
void Update() {
while (jobs.Count > 0)
jobs.Dequeue().Invoke();
}
internal void AddJob(Action newJob) {
jobs.Enqueue(newJob);
}
}
Теперь из вашего кода вы можете просто позвонить ...
UnityMainThread.wkr.AddJob();
... чтобы ваш код оставался легко читаемым(и управлять), как показано ниже ...
public void SignInWithEmail() {
auth.SignInWithEmailAndPasswordAsync(email, password).ContinueWith(task => {
DatabaseReference.GetValueAsync().ContinueWith(task => {
UnityMainThread.wkr.AddJob(() => {
// Will run on main thread, hence issue is solved
userPanel.SetActive(true);
authPanel.SetActive(false);
})
}
}
}