只能从主线程调用SetActive() [英] SetActive() can only be called from the main thread

查看:439
本文介绍了只能从主线程调用SetActive()的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I am stuck with this problem for 3 days, I did a lot of research, but couldn't find any answer, Here is a brief explanation of what is happening, trying to work with Firebase Database and Authentication with Unity3D, Here are the steps:

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

First user signs in and if it's successful, it will fetch user's data from Database and then Authentication panel should be deactivated and User's panel activated.

当尝试设置Active面板时,它给了我这个错误.

It Gives me this error when trying to SetActive panel.

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

SetActive can only be called from the main thread. Constructors and field initializers will be executed from the loading thread when loading a scene. Don't use this function in the constructor or field initializers, instead move initialization code to the Awake or Start function. 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);
    }
  }
}

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

I'm not trying to load another scene or anything else.

推荐答案

因此,我的答案与 Milod's 所接受的答案非常相似,但与即使他仍然可以工作,我还是花了好一会儿才把头缠住他.

So my answer is very similar to the accepted answer from Milod's, but a little different, as it took me a while to wrap my head around his, even though his still works.

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

  1. The Issue: Normally, all your code runs on a single thread in Unity, since Unity is single-threaded, however when working with APIs like Firebase, which require callbacks, the callback functions will be handled by a new thread. This can lead to race-conditions, especially on a single-threaded engine like Unity.

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

The solution (from Unity): Starting from Unity 2017.X, unity now requires changes to UI components to be run on the Main thread (i.e. the first thread that was started with Unity).

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

What is impacted ?: Mainly calls that modify the UI like...

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

    1. 建议的解决方案: 对于那些需要回调函数的调用,例如...
    1. Suggested Solution: For those calls which require callback functions, like...

    DatabaseReference.GetValueAsync()
    

    ...你可以...

    ...you can...

    • 将它们发送到设置为在该初始线程上运行的函数.
    • ...并且它使用队列来确保它们将按照添加的顺序运行.
    • ...并按照Unity团队的建议使用单例模式.
    1. 将以下代码放到您将始终启用的gameObject上的场景中,以使工作人员能够...
      • 始终在本地线程上运行
      • 可以将那些回调函数发送给
      • 以便在本地线程上运行.
    1. Place the code below into your scene on a gameObject that will always be enabled, so that you have a worker that...
      • always runs on the local thread
      • can be sent those callback functions to be run on the local thread.

    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. 现在从您的代码中,您只需调用...

    1. Now from your code, you can simply call...

     UnityMainThread.wkr.AddJob();
    

  • ...以便您的代码保持易于阅读(和管理),如下所示...

    ...so that your code remains easy to read (and manage), as shown below...

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

    这篇关于只能从主线程调用SetActive()的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆