字段初始化后无法从主线程访问对象

时间:2016-03-24 17:13:24

标签: c# multithreading unity3d

我们正在制作一个统一游戏,它使用在tcp套接字上接收的命令来处理游戏某个校准状态下的动作。 当接收到新字符串时,statemanager处理由socketmanager引发的事件。然后,这个状态管理器必须在一开始就在一个字段中引用的游戏对象上触发一个方法。

我们现在面临的问题是处理这些事件的线程无法访问此对象。我们收到以下错误:

  

ToString只能从主线程调用。加载场景时,将从加载线程执行构造函数和字段初始值设定项。

在标有此行给出错误

的行上

Unity如何使用EventHandlers处理线程,我们如何访问此对象?

提前致谢!

public class StateManager : MonoBehaviour {

private bool initialized;
private GameObject calibrationController;


void Start(){
    InitializeEventHandler ();
}

void OnLevelWasLoaded(int level) {
    if (level == 3) {
        calibrationController = GameObject.FindGameObjectWithTag("CalibrationController");
        Debug.Log ("calibrationController 1: " + calibrationController);
        calibrationController.GetComponent<CalibrationController> ().NewState += NewCalibrationState;
        calibrationController.GetComponent<CalibrationController>().setupCalibration();
    }
}

private void InitializeEventHandler(){
    GetComponent<GameSocket> ().NewCommand += NewCommandReceived;
}

private void NewCommandReceived(object sender, NewCommandEventArgs e){
    HandleCommandReceived (e.Command);
}

private void NewCalibrationState(object sender, NewCalibrationStateEventArgs e)
{
    HandleNewCalibrationState (e.State);
}

private void HandleCommandReceived(string command){
    switch (command) {
    case "startcalibrationcomplete":
        Debug.Log ("startcalibrationcomplete");
        Debug.Log ("calibrationController 2: " + calibrationController); THIS LINE GIVES THE ERROR !!!
        Debug.Log(GameObject.FindGameObjectWithTag("CalibrationController"));
            break;
    default:
            Debug.Log ("state10");
        break;
    }
}

private void HandleNewCalibrationState(string state){
    switch (state) {
    case "startcalibration":
        GetComponent<GameSocket>().MySend("startcalibration");
        // ...
        break;
    case "animationdone":
        GetComponent<GameSocket>().MySend("animationdone");
        break;
    default:
        Debug.Log ("state10");
        break;
    }
}
  

对于未来的读者:由于Unity的线程不佳,不可能从事件处理程序调用游戏对象的方法。   通过让事件处理程序在空的游戏对象上设置数据脚本的属性,我找到了一种解决方法。   可以从任何游戏对象(即更新周期)访问此脚本上的数据。

1 个答案:

答案 0 :(得分:0)

事实是 Unity 支持框 out 。几年前,当他们从另一个线程调用时,他们将API更改为抛出异常。

提升来自另一个主题的事件时,在该主题中调用事件 不在 Unity / 线程中。这意味着在调用事件时,您仍然无法使用Unity来自该事件回调的API具有主题 限制

我在Unity中使用Threads进行了大量实验,并得出结论以下是Thread 可能与Unity API 集成的方式。

要在Unity中使用主题,您必须使用lock,一个可以从Unity 主要 boolean全局变量>线程和您创建的TCP线程。当您接收来自网络的内容时,您还需要一种方法来调用 Unity API 。使用lockboolean variables完成。然后,您可以从更新 功能调用 Unity API ,而不是新创建的主题

主线程中创建TCP instance,然后尝试从另一个主题访问它。另一个线程中的创建访问 TCP实例。如果你这样做,你将遇到崩溃/冻结或错误。

下面的代码应该向您展示如何在Unity中使用TCP和Thread引发事件。以下代码的目标是来自主线程(更新功能)的引发事件,而不是另一个线程。您可以根据需要为此代码添加任意数量的事件。

public class TCPRECEIVER: MonoBehaviour
{

    readonly object locker = new object(); //For Locking variables
    bool continueReading = false;
    bool gotNewMessage = false;

    byte[] receivedBytes; //Stores bytes received from the server (will be accessed from Multiple Threads with lock)


    //Event to notify other functions when something is received
    public delegate void newMessageReceieved(byte[] bytesFromServer);
    public static event newMessageReceieved onNewMessageReceieved;


    public void Start()
    {
        receivedBytes = new byte[40];

    }


    void Update()
    {
        //Lock is expensive so make sure that we are still in reading mode before locking
        if (continueReading)
        {
            //lock variables
            lock (locker)
            {
                //Check if there is a new message
                if (gotNewMessage)
                {
                    gotNewMessage = false; //Set to false so that we don't run this again until we receive from server again

                    //Raise the event here if there are subscribers
                    if (onNewMessageReceieved != null)
                    {
                        onNewMessageReceieved(receivedBytes);
                    }
                }
            }
        }
    }

    //Start Reading from Server
    void startReading()
    {
        continueReading = true;

        //Start new Thread
        new System.Threading.Thread(() =>
        {
            //Create Client outside the loo
            System.Net.Sockets.TcpClient tcpClient = new System.Net.Sockets.TcpClient("192.168.1.1", 8090);
            System.Net.Sockets.NetworkStream tcpStream = tcpClient.GetStream();

            //Read Forever until stopReading is called
            while (continueReading)
            {
                byte[] bytesToRead = new byte[tcpClient.ReceiveBufferSize];
                int bytesRead = tcpStream.Read(bytesToRead, 0, tcpClient.ReceiveBufferSize);

                //Check if we received anything from server
                if (bytesRead > 0)
                {
                    //lock variables
                    lock (locker)
                    {
                        //Copy the recived data to the Global variable "receivedBytes"
                        System.Buffer.BlockCopy(bytesToRead, 0, receivedBytes, 0, bytesRead);

                        //Notify the Update function that we got something
                        gotNewMessage = true;
                    }
                }
                System.Threading.Thread.Sleep(1); //So that we don't lock up
            }
        }).Start();
    }

    //Stop reading
    void stopReading()
    {
        continueReading = false;
    }
}

然后您可以订阅和取消订阅来自其他类的事件/消息:

public void OnEnable()
{
    //Subscribe to the event
    TCPRECEIVER.onNewMessageReceieved += receivedBytesFromServer;
}

public void OnDisable()
{
    //Un-Subscribe to the event
    TCPRECEIVER.onNewMessageReceieved -= receivedBytesFromServer;
}


void receivedBytesFromServer(byte []bytesFromServer)
{
  //Do something with the bytes
}