web-dev-qa-db-fra.com

Utiliser l'API Unity à partir d'un autre thread ou appeler une fonction dans le thread principal

Mon problème est que j'essaie d'utiliser Unity socket pour implémenter quelque chose. Chaque fois que je reçois un nouveau message, je dois le mettre à jour avec le texte de mise à jour (il s'agit d'un texte d'unité). Cependant, lorsque je fais le code suivant, la mise à jour de void n'appelle pas à chaque fois. 

La raison pour laquelle je n'inclue pas updatetext.GetComponent<Text>().text = "From server: "+tempMesg; dans le void getInformation est que cette fonction est dans le fil. Lorsque j'inclus cela dans getInformation (), cela entraîne une erreur: 

getcomponentfastpath can only be called from the main thread

Je pense que le problème est que je ne sais pas comment exécuter le thread principal et le thread enfant en C # ensemble? Ou il y a peut-être d'autres problèmes ... J'espère que quelqu'un pourra vous aider ... Il y a mon code:

using UnityEngine;
using System.Collections;
using System;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using UnityEngine.UI;


public class Client : MonoBehaviour {

    System.Net.Sockets.TcpClient clientSocket = new System.Net.Sockets.TcpClient();
    private Thread oThread;

//  for UI update
    public GameObject updatetext;
    String tempMesg = "Waiting...";

    // Use this for initialization
    void Start () {
        updatetext.GetComponent<Text>().text = "Waiting...";
        clientSocket.Connect("10.132.198.29", 8888);
        oThread = new Thread (new ThreadStart (getInformation));
        oThread.Start ();
        Debug.Log ("Running the client");
    }

    // Update is called once per frame
    void Update () {
        updatetext.GetComponent<Text>().text = "From server: "+tempMesg;
        Debug.Log (tempMesg);
    }

    void getInformation(){
        while (true) {
            try {
                NetworkStream networkStream = clientSocket.GetStream ();
                byte[] bytesFrom = new byte[10025];
                networkStream.Read (bytesFrom, 0, (int)bytesFrom.Length);
                string dataFromClient = System.Text.Encoding.ASCII.GetString (bytesFrom);
                dataFromClient = dataFromClient.Substring (0, dataFromClient.IndexOf ("$"));
                Debug.Log (" >> Data from Server - " + dataFromClient);

                tempMesg = dataFromClient;

                string serverResponse = "Last Message from Server" + dataFromClient;

                Byte[] sendBytes = Encoding.ASCII.GetBytes (serverResponse);
                networkStream.Write (sendBytes, 0, sendBytes.Length);
                networkStream.Flush ();
                Debug.Log (" >> " + serverResponse);

            } catch (Exception ex) {
                Debug.Log ("Exception error:" + ex.ToString ());
                oThread.Abort ();
                oThread.Join ();
            }
//          Thread.Sleep (500);
        }
    }
}
14
user6142261

Une grande partie de l'écriture sur les discussions dans Unity est complètement, totalement, incorrecte.

Le fait est qu'Unity est (bien entendu) totalement basé sur des cadres.

Lorsque vous travaillez dans un système basé sur des images, les problèmes de threading sont spectaculairement différents (et généralement beaucoup plus simples) que dans un système conventionnel.

Les problèmes de threading sur un système basé sur des cadres sont totalement différents de ceux d'un système conventionnel. (À certains égards, il est beaucoup plus facile de traiter car certains problèmes n'existent tout simplement pas dans un système basé sur des cadres.)

Disons que vous avez un affichage de thermomètre Unity qui montre une valeur

Thermo.cs

 enter image description here

Donc, il aura une fonction qui s'appelle dans Update, comme

func void ShowThermoValue(float fraction) {
   display code, animates a thermometer
}

Cela ne fonctionne qu'une fois par image, et c'est tout.

Il est très facile d'oublier ce fait fondamental.

Bien sûr, il ne fonctionne que sur le "thread principal".

(Il n'y a rien d'autre dans Unity! Il y a juste ............... "le fil Unity"!)

Mais le paradigme clé à garder à l'esprit est le suivant: il s'exécute une fois par image.

Maintenant, ailleurs, dans Thermo.cs, vous aurez une fonction qui gère le concept de "une nouvelle valeur est arrivée":

[MonoPInvokeCallback(typeof(ipDel))]
public static void NewValueArrives(float f) {

    ... ???
}

Notez que, bien sûr, c'est une fonction de classe!

Vous ne pouvez en aucune manière "atteindre" une fonction Unity normale telle que ShowThermoValue !!!!!! Note de bas de page 1

C'est un concept totalement, complètement, sans signification. Vous ne pouvez pas "atteindre" ShowThermoValue. 

Tous les exemples de code "liés aux threads" que vous voyez, pour Unity, qui tentent "d'atteindre" un "thread" sont totalement et complètement égarés.

Il n'y a pas de fil à atteindre!

Encore une fois - et cela est surprenant - si vous venez de rechercher sur Google tous les écrits sur les discussions dans Unity:

Beaucoup d'articles et de posts sont littéralement totalement, totalement incorrects.

Disons: les valeurs arrivent très fréquemment et irrégulièrement.

Vous pourriez avoir divers appareils scientifiques connectés à un bâti de PC et d’iPhone, ainsi que de nouvelles valeurs de "température" pourrait arriver très souvent ... des dizaines de fois par image ... ou peut-être seulement toutes les quelques secondes.

Alors, que faites-vous avec les valeurs?

Cela ne pourrait pas être plus simple.

A partir du thread arrive-values, tout ce que vous faites est ................. attendez-le ............. définissez une variable dans le composant !!

WTF? Tout ce que vous faites est définir une variable? C'est tout? Comment cela peut-il être aussi simple?

C'est l'une de ces situations inhabituelles:

  1. Une grande partie de l'écriture sur les discussions dans Unity est juste complètement faux. Ce n'est pas comme s'il y avait "une erreur" ou "quelque chose qui doit être corrigé". C'est juste "totalement et complètement faux": au niveau du paradigme de base.

  2. Étonnamment, l'approche actuelle est absurdement simple.

  3. C'est si simple que vous pensez peut-être que vous faites quelque chose de mal.

Alors avoir la variable ...

[System.Nonserialized] public float latestValue;

Réglez-le à partir du "fil d'arrivée" ...

[MonoPInvokeCallback(typeof(ipDel))]
public static void NewValueArrives(float f) {

    ThisScript.runningInstance.latestValue = f; // done
}

Honnêtement c'est tout. 

Essentiellement, pour être le plus grand expert du monde en matière de "threading in Unity" - qui est, bien sûr, basé sur des cadres - il n’ya rien de plus à faire que ce qui précède.

Et chaque fois que ShowThermoValue est appelé chaque image ...................... affichez simplement cette valeur!

Vraiment c'est ça!

[System.Nonserialized] public float latestValue;
func void ShowThermoValue() { // note NO arguments here!
   display code, animates a thermometer
   thermo height = latestValue
}

Vous affichez simplement la "dernière" valeur.

latestValue peut avoir été défini une fois, deux fois, dix fois ou cent fois ce cadre ............ mais vous affichez simplement quelle que soit la valeur lorsque ShowThermoValue exécute ce cadre! 

Que pourriez-vous afficher?

Le thermomètre est mis à jour à 60 images par seconde à l'écran. Note de bas de page 2

C'est aussi simple que ça. C'est aussi simple que ça. Surprenant mais vrai.

Parfois, les programmeurs de threading expérimentés se lient avec un système basé sur des cadres, car: dans un système basé sur des cadres, la plupart des considérations de verrouillage et de piste de course ... n'existent pas de manière conceptuelle.

Dans un système basé sur des images, tous les éléments de jeu doivent simplement être affichés ou se comporter en fonction d'une "valeur actuelle" définie quelque part. Si vous avez des informations provenant d'autres threads, définissez simplement ces valeurs - done.

Vous ne pouvez pas réellement} _ "parler au fil principal" dans Unity parce que ce fil principal ............. est basé sur les cadres!

Laissant de côté les problèmes de threads, un système continu ne peut pas parler de manière significative à un système de paradigme de cadre. 

La plupart des problèmes de verrouillage, blocage et hippodromes sont inexistants dans le paradigme basé sur les cadres, car: si vous définissez la dernière valeurValue dix fois, un million de fois, un milliard de fois dans un cadre particulier. . Que pouvez-vous faire? .. vous ne pouvez afficher qu'une seule valeur!

Pensez à un film plastique à l'ancienne. Vous avez littéralement juste ...... un cadre, et c'est tout. Si vous définissez lastValue mille milliards de fois dans un cadre particulier, ShowThermoValue affichera simplement (pendant 60 secondes) la valeur capturée lors de son exécution.

Tout ce que vous faites est: laissez des informations quelque part, le système de paradigme de cadre utilisera ce cadre s'il le souhaite.

Voilà en quelques mots.Ainsi, la plupart des "problèmes de threading" disparaissent dans Unity.

Tout ce que vous pouvez faire à partir de 

 

  •  

  • sont simplement des "valeurs de chute" que le jeu peut utiliser.

c'est tout!.


Notes de bas de page


 Comment peux-tu? En tant qu'expérience de réflexion, oubliez le problème selon lequel vous êtes sur un fil différent. ShowThermoValue est exécuté une fois par image par le moteur d'images. Vous ne pouvez pas "appeler" cela de manière significative. Contrairement au logiciel OO normal, vous ne pouvez pas, par exemple, instancier une instance de la classe (un composant ?? sans signification) et exécuter cette fonction, ce qui est totalement dénué de sens. Dans la programmation par thread "normale", les threads peuvent parler en arrière, en avant, etc., ce qui vous inquiète au sujet du verrouillage, de l'hippodrome, etc. Mais tout cela est (sans signification} _ dans un système ECS, basé sur des cadres. Il n'y a rien à "parler".

Disons qu'Unity était en fait multithread !!!! Donc, les gars de Unity ont tout le moteur en marche de manière multithread. Cela ne ferait aucune différence - vous ne pouvez pas entrer "dans" ShowThermoValue de manière significative! C'est un composant que le moteur de trames exécute une fois par trame et c'est tout.

Donc, NewValueArrives n’est nulle part; c’est une fonction de classe!.


 Je me réfère au "fil d'arrivée" ... en fait! NewValueArrives peut ou non fonctionner sur le thread principal !!!! Il peut fonctionner sur le thread du plugin, ou sur un autre thread! En fait, il se peut qu’il soit complètement mono-thread au moment où vous traitez avec l’appel NewValueArrives! Cela n'a pas d'importance! Ce que vous faites, et tout ce que vous pouvez faire, dans un paradigme basé sur un cadre, est de "laisser traîner" des informations que des composants tels que ShowThermoValue peuvent utiliser comme bon leur semble.

2
Fattie

J'ai utilisé cette solution à ce problème. Créez un script avec ce code et attachez-le à un objet de jeu:

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

public class ExecuteOnMainThread : MonoBehaviour {

    public readonly static Queue<Action> RunOnMainThread = new Queue<Action>();

    void Update()
    {
        while (RunOnMainThread.Count > 0)
        {
            RunOnMainThread.Dequeue().Invoke();
        }
    }

}

Ensuite, lorsque vous devez appeler quelque chose sur le thread principal et accéder à l'API Unity à partir de toute autre fonction de votre application:

ExecuteOnMainThread.RunOnMainThread.Enqueue(() => {

    // Code here will be called in the main thread...

});
1
Pelayo Méndez