SetActive()只能从主线程调用

时间:2018-12-24 18:06:39

标签: c# firebase unity3d

我在这个问题上呆了三天,我做了很多研究,但找不到任何答案,这是正在发生的事情的简要说明,尝试使用Firebase数据库和Unity3D进行身份验证,在这里步骤如下:

第一个用户登录,如果登录成功,它将从数据库中获取用户的数据,然后应禁用“身份验证”面板并激活“用户”面板。

尝试设置Active面板时,出现此错误。

SetActive只能从主线程调用。 加载场景时,将从加载线程执行构造函数和字段初始化程序。 不要在构造函数或字段初始化程序中使用此函数,而应将初始化代码移至Awake或Start函数。 UnityEngine.GameObject:SetActive(布尔)

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);
    }
  }
}

我不是要加载其他场景或其他任何内容。

如有需要,我可以提供更多信息

3 个答案:

答案 0 :(得分:1)

因此基本上UI元素需要在Main线程中进行修改,我发现此脚本可以在Main线程中执行您的函数,只需将您的函数放入协程入队< / strong>到脚本(UnityMainThreadDispatcher)。 (您需要在场景中添加一个对象,然后向其中添加MainThreadDispathcer脚本)

这是我的函数的外观:

byte[] imageData = Files.readAllBytes(Paths.get("testImage4.jpg"));

这是在Main Thead中运行它的脚本

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;
}

答案 1 :(得分:1)

所以我的答案与 Milod's 所接受的答案非常相似,但是有些不同,尽管我花了一些时间将自己的头缠在他的身上,尽管他仍然在工作。

  1. 问题: 通常,您的所有代码都在Unity中的单个线程上运行,因为Unity是单线程的, 但是,当使用Firebase等需要回调的API时,回调函数将由新线程处理。 这可能会导致竞争条件,尤其是在像Unity这样的单线程引擎上。

  2. 解决方案(来自Unity): 从Unity 2017.X开始,Unity现在需要对UI组件进行更改才能在Main线程(即从Unity启动的第一个线程)上运行。

  3. 受影响的是什么?: 主要是修改UI的调用,例如...

    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);
    }
  }
}

  1. 建议的解决方案: 对于那些需要回调函数的调用,例如...
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);
    }
}

  1. 现在从您的代码中,您只需调用...

     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);            
        })

    }
  }
}

答案 2 :(得分:0)

请注意,Firebase 现在有一个很好的 ContinueWithOnMainThread 扩展方法,它比其他建议的答案更优雅地解决了这个问题:

using Firebase.Extensions;
public void SignInWithEmail() {
    // This code runs on the caller's thread.
    auth.SignInWithEmailAndPasswordAsync(email, password).ContinueWith(task => {
      // This code runs on an arbitrary thread.
      DatabaseReference.GetValueAsync().ContinueWithOnMainThread(task => {
        // This code runs on the Main thread. No problem.
        userPanel.SetActive(true);
        authPanel.SetActive(false);
    }
  }
}```