SetActive () может быть вызван только из основного потока - PullRequest
0 голосов
/ 24 декабря 2018

Я застрял с этой проблемой в течение 3 дней, я провел много исследований, но не смог найти никакого ответа, Вот краткое объяснение того, что происходит, пытаясь работать с базой данных Firebase и Аутентификация с Unity3D, ЗдесьВот шаги:

Первый пользователь входит в систему и, если он успешен, он получает данные пользователя из базы данных, а затем панель аутентификации должна быть деактивирована и панель пользователя активирована.

Это выдает мне эту ошибку при попытке установки панели SetActive.

SetActive можно вызывать только из основного потока.Конструкторы и инициализаторы полей будут выполняться из потока загрузки при загрузке сцены.Не используйте эту функцию в конструкторе или инициализаторах поля, вместо этого переместите код инициализации в функцию «Пробуждение» или «Запуск».UnityEngine.GameObject: SetActive (Boolean)

public void SignInWithEmail()
{
    auth.SignInWithEmailAndPasswordAsync(email, password).ContinueWith(task => {
      DatabaseReference.GetValueAsync().ContinueWith(task => {

        //here after successful signing in, it gets the data from the Database
        //and after that it should activate the user panel
        //and deactivate the authentication panel

        //HERE IS THE PROBLEM 
        userPanel.SetActive(true);
        authPanel.SetActive(false);
    }
  }
}

Я не пытаюсь загрузить другую сцену или что-либо еще.

При необходимости могу предоставить дополнительную информацию

Ответы [ 2 ]

0 голосов
/ 22 июня 2019

Таким образом, мой ответ очень похож на принятый ответ от Милода , но немного по-другому, так как мне потребовалось некоторое время, чтобы обернуть мою голову вокруг него, хотя он все ещеработает.

  1. Проблема: Обычно весь ваш код выполняется в Unity с одним потоком, поскольку Unity является однопоточным, однако при работе с такими API, как Firebase, для которых требуются обратные вызовы, функции обратного вызова будут обрабатываться новым потоком.Это может привести к гоночным условиям, особенно на однопоточном двигателе, таком как Unity.

  2. Решение (от Unity): Начиная с Unity 2017.X,Unity теперь требует изменения компонентов пользовательского интерфейса для запуска в главном потоке (то есть в первом потоке, который был запущен с Unity).

  3. Что влияет?: Преимущественновызовы, которые изменяют пользовательский интерфейс, например ...

    gameObject.SetActive(true);  // (or false)
    textObject.Text = "some string" // (from UnityEngine.UI)
    
  4. Как это относится к вашему коду:

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.

Фактическое решение

  1. Поместитекод ниже в вашей сцене на 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);            
        })

    }
  }
}
0 голосов
/ 25 декабря 2018

Таким образом, в основном элементы пользовательского интерфейса необходимо изменить в главном потоке, и я нашел этот скрипт, и он выполнит вашу функцию в основном потоке, просто поместите вашу функцию в Coroutine и Enqueue это к сценарию (UnityMainThreadDispatcher).(Вам нужен объект на сцене и добавьте в него скрипт MainThreadDispathcer)

Вот как выглядела моя функция:

public void SignInWithEmail()
{
    auth.SignInWithEmailAndPasswordAsync(email, password).ContinueWith(task => {
     DatabaseReference.GetValueAsync().ContinueWith(task => {
         //Here's the fix
         UnityMainThreadDispatcher.Instance().Enqueue(ShowUserPanel());
    }
  }
}


public IEnumerator ShowUserPanel()
{
    uiController.userPanel.panel.SetActive(true);
    uiController.authPanel.SetActive(false);
    yield return null;
}

Это скриптзапустить его в Main Thead

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;


public class UnityMainThreadDispatcher : MonoBehaviour {

private static readonly Queue<Action> _executionQueue = new Queue<Action>();


/// <summary>
/// Locks the queue and adds the IEnumerator to the queue
/// </summary>
/// <param name="action">IEnumerator function that will be executed from the main thread.</param>
public void Enqueue(IEnumerator action) {
    lock (_executionQueue) {
        _executionQueue.Enqueue (() => {
            StartCoroutine (action);
        });
    }
}

/// <summary>
/// Locks the queue and adds the Action to the queue
/// </summary>
/// <param name="action">function that will be executed from the main thread.</param>
public void Enqueue(Action action)
{
    Enqueue(ActionWrapper(action));
}
IEnumerator ActionWrapper(Action a)
{
    a();
    yield return null;
}


private static UnityMainThreadDispatcher _instance = null;

public static bool Exists() {
    return _instance != null;
}

public static UnityMainThreadDispatcher Instance() {
    if (!Exists ()) {
        throw new Exception ("UnityMainThreadDispatcher could not find the UnityMainThreadDispatcher object. Please ensure you have added the MainThreadExecutor Prefab to your scene.");
    }
    return _instance;
}

void Awake() {
    if (_instance == null) {
        _instance = this;
        DontDestroyOnLoad(this.gameObject);
    }
}

public void Update() {
    lock(_executionQueue) {
        while (_executionQueue.Count > 0) {
            _executionQueue.Dequeue().Invoke();
        }
    }
}

void OnDestroy() {
        _instance = null;
}

}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...