Mein Problem ist, ich versuche, Unity-Socket zu verwenden, um etwas zu implementieren. Jedes Mal, wenn ich eine neue Nachricht erhalte, muss ich sie auf den Updattext aktualisieren (es handelt sich um einen Unity-Text). Wenn ich den folgenden Code durchführe, ruft das Void-Update jedoch nicht jedes Mal auf.
Der Grund dafür, dass ich updatetext.GetComponent<Text>().text = "From server: "+tempMesg;
nicht in die void getInformation einbeziehe, ist diese Funktion im Thread. Wenn ich das in getInformation () einbeziehe, kommt es zu einem Fehler:
getcomponentfastpath can only be called from the main thread
Ich denke, das Problem ist, dass ich nicht weiß, wie man den Haupt-Thread und den untergeordneten Thread in C # zusammen ausführt. Oder gibt es vielleicht andere Probleme ... Hoffe, jemand kann helfen ... Es gibt meinen 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);
}
}
}
Unity ist nicht Thread
sicher, daher haben sie beschlossen, es unmöglich zu machen, ihre API von einer anderen Thread
aufzurufen, indem sie einen Mechanismus zum Auslösen einer Ausnahme hinzufügten, wenn ihre API von einer anderen Thread
verwendet wird. ].
Diese Frage wurde so oft gestellt, aber es gab keine richtige Lösung/Antwort auf eine von ihnen. Die Antworten lauten normalerweise "benutze ein Plugin" oder mache etwas, das nicht threadsicher ist. Hoffentlich ist dies der letzte.
Die Lösung, die Sie normalerweise auf der Forum-Website von Stackoverflow oder Unity finden, besteht darin, einfach eine boolean
-Variable zu verwenden, um dem Haupt-Thread mitzuteilen, dass Sie Code in der Haupt-Thread
-Variable ausführen müssen. Dies ist nicht richtig, da es nicht thread-safe ist und Ihnen keine Kontrolle darüber gibt, welche Funktion aufgerufen werden soll. Was ist, wenn Sie mehrere Threads
haben, die den Haupt-Thread benachrichtigen müssen?
Eine andere Lösung, die Sie sehen werden, ist die Verwendung einer Coroutine anstelle einer Thread
. Dies funktioniert nicht . Die Verwendung von Coroutine für Sockel ändert nichts. Sie werden immer noch mit Ihren Einfrieren Problemen enden. Sie müssen sich an Ihren Thread
- Code halten oder Async
verwenden.
Zu diesem Zweck können Sie eine Sammlung wie List
erstellen. Wenn Sie im Haupt-Thread etwas ausführen möchten, rufen Sie eine Funktion auf, die den auszuführenden Code in einem Action
speichert. Kopieren Sie das List
von Action
in ein lokales List
von Action
und führen Sie den Code aus dem lokalen Action
in diesem List
löschen Sie dann das List
. Dies verhindert, dass andere Threads
warten müssen, bis die Ausführung abgeschlossen ist.
Sie müssen auch einen volatile boolean
Hinzufügen, um die Funktion Update
zu benachrichtigen, dass in dem auszuführenden List
Code wartet. Wenn Sie die List
in eine lokale List
kopieren, sollte dies um das Schlüsselwort lock
herum erfolgen, um zu verhindern, dass ein anderer Thread darauf schreibt.
Ein Skript, das das ausführt, was ich oben erwähnt habe:
UnityThread
Skript:
#define ENABLE_UPDATE_FUNCTION_CALLBACK
#define ENABLE_LATEUPDATE_FUNCTION_CALLBACK
#define ENABLE_FIXEDUPDATE_FUNCTION_CALLBACK
using System;
using System.Collections;
using UnityEngine;
using System.Collections.Generic;
public class UnityThread : MonoBehaviour
{
//our (singleton) instance
private static UnityThread instance = null;
////////////////////////////////////////////////UPDATE IMPL////////////////////////////////////////////////////////
//Holds actions received from another Thread. Will be coped to actionCopiedQueueUpdateFunc then executed from there
private static List<System.Action> actionQueuesUpdateFunc = new List<Action>();
//holds Actions copied from actionQueuesUpdateFunc to be executed
List<System.Action> actionCopiedQueueUpdateFunc = new List<System.Action>();
// Used to know if whe have new Action function to execute. This prevents the use of the lock keyword every frame
private volatile static bool noActionQueueToExecuteUpdateFunc = true;
////////////////////////////////////////////////LATEUPDATE IMPL////////////////////////////////////////////////////////
//Holds actions received from another Thread. Will be coped to actionCopiedQueueLateUpdateFunc then executed from there
private static List<System.Action> actionQueuesLateUpdateFunc = new List<Action>();
//holds Actions copied from actionQueuesLateUpdateFunc to be executed
List<System.Action> actionCopiedQueueLateUpdateFunc = new List<System.Action>();
// Used to know if whe have new Action function to execute. This prevents the use of the lock keyword every frame
private volatile static bool noActionQueueToExecuteLateUpdateFunc = true;
////////////////////////////////////////////////FIXEDUPDATE IMPL////////////////////////////////////////////////////////
//Holds actions received from another Thread. Will be coped to actionCopiedQueueFixedUpdateFunc then executed from there
private static List<System.Action> actionQueuesFixedUpdateFunc = new List<Action>();
//holds Actions copied from actionQueuesFixedUpdateFunc to be executed
List<System.Action> actionCopiedQueueFixedUpdateFunc = new List<System.Action>();
// Used to know if whe have new Action function to execute. This prevents the use of the lock keyword every frame
private volatile static bool noActionQueueToExecuteFixedUpdateFunc = true;
//Used to initialize UnityThread. Call once before any function here
public static void initUnityThread(bool visible = false)
{
if (instance != null)
{
return;
}
if (Application.isPlaying)
{
// add an invisible game object to the scene
GameObject obj = new GameObject("MainThreadExecuter");
if (!visible)
{
obj.hideFlags = HideFlags.HideAndDontSave;
}
DontDestroyOnLoad(obj);
instance = obj.AddComponent<UnityThread>();
}
}
public void Awake()
{
DontDestroyOnLoad(gameObject);
}
//////////////////////////////////////////////COROUTINE IMPL//////////////////////////////////////////////////////
#if (ENABLE_UPDATE_FUNCTION_CALLBACK)
public static void executeCoroutine(IEnumerator action)
{
if (instance != null)
{
executeInUpdate(() => instance.StartCoroutine(action));
}
}
////////////////////////////////////////////UPDATE IMPL////////////////////////////////////////////////////
public static void executeInUpdate(System.Action action)
{
if (action == null)
{
throw new ArgumentNullException("action");
}
lock (actionQueuesUpdateFunc)
{
actionQueuesUpdateFunc.Add(action);
noActionQueueToExecuteUpdateFunc = false;
}
}
public void Update()
{
if (noActionQueueToExecuteUpdateFunc)
{
return;
}
//Clear the old actions from the actionCopiedQueueUpdateFunc queue
actionCopiedQueueUpdateFunc.Clear();
lock (actionQueuesUpdateFunc)
{
//Copy actionQueuesUpdateFunc to the actionCopiedQueueUpdateFunc variable
actionCopiedQueueUpdateFunc.AddRange(actionQueuesUpdateFunc);
//Now clear the actionQueuesUpdateFunc since we've done copying it
actionQueuesUpdateFunc.Clear();
noActionQueueToExecuteUpdateFunc = true;
}
// Loop and execute the functions from the actionCopiedQueueUpdateFunc
for (int i = 0; i < actionCopiedQueueUpdateFunc.Count; i++)
{
actionCopiedQueueUpdateFunc[i].Invoke();
}
}
#endif
////////////////////////////////////////////LATEUPDATE IMPL////////////////////////////////////////////////////
#if (ENABLE_LATEUPDATE_FUNCTION_CALLBACK)
public static void executeInLateUpdate(System.Action action)
{
if (action == null)
{
throw new ArgumentNullException("action");
}
lock (actionQueuesLateUpdateFunc)
{
actionQueuesLateUpdateFunc.Add(action);
noActionQueueToExecuteLateUpdateFunc = false;
}
}
public void LateUpdate()
{
if (noActionQueueToExecuteLateUpdateFunc)
{
return;
}
//Clear the old actions from the actionCopiedQueueLateUpdateFunc queue
actionCopiedQueueLateUpdateFunc.Clear();
lock (actionQueuesLateUpdateFunc)
{
//Copy actionQueuesLateUpdateFunc to the actionCopiedQueueLateUpdateFunc variable
actionCopiedQueueLateUpdateFunc.AddRange(actionQueuesLateUpdateFunc);
//Now clear the actionQueuesLateUpdateFunc since we've done copying it
actionQueuesLateUpdateFunc.Clear();
noActionQueueToExecuteLateUpdateFunc = true;
}
// Loop and execute the functions from the actionCopiedQueueLateUpdateFunc
for (int i = 0; i < actionCopiedQueueLateUpdateFunc.Count; i++)
{
actionCopiedQueueLateUpdateFunc[i].Invoke();
}
}
#endif
////////////////////////////////////////////FIXEDUPDATE IMPL//////////////////////////////////////////////////
#if (ENABLE_FIXEDUPDATE_FUNCTION_CALLBACK)
public static void executeInFixedUpdate(System.Action action)
{
if (action == null)
{
throw new ArgumentNullException("action");
}
lock (actionQueuesFixedUpdateFunc)
{
actionQueuesFixedUpdateFunc.Add(action);
noActionQueueToExecuteFixedUpdateFunc = false;
}
}
public void FixedUpdate()
{
if (noActionQueueToExecuteFixedUpdateFunc)
{
return;
}
//Clear the old actions from the actionCopiedQueueFixedUpdateFunc queue
actionCopiedQueueFixedUpdateFunc.Clear();
lock (actionQueuesFixedUpdateFunc)
{
//Copy actionQueuesFixedUpdateFunc to the actionCopiedQueueFixedUpdateFunc variable
actionCopiedQueueFixedUpdateFunc.AddRange(actionQueuesFixedUpdateFunc);
//Now clear the actionQueuesFixedUpdateFunc since we've done copying it
actionQueuesFixedUpdateFunc.Clear();
noActionQueueToExecuteFixedUpdateFunc = true;
}
// Loop and execute the functions from the actionCopiedQueueFixedUpdateFunc
for (int i = 0; i < actionCopiedQueueFixedUpdateFunc.Count; i++)
{
actionCopiedQueueFixedUpdateFunc[i].Invoke();
}
}
#endif
public void OnDisable()
{
if (instance == this)
{
instance = null;
}
}
}
[~ # ~] Verwendung [~ # ~] :
Mit dieser Implementierung können Sie Funktionen in den 3 am häufigsten verwendeten Unity-Funktionen aufrufen: Update
, LateUpdate
und FixedUpdate
funktioniert. Auf diese Weise können Sie auch eine Coroutine-Funktion im Hauptfenster Thread
aufrufen. Es kann erweitert werden, um Funktionen in anderen Unity-Rückruffunktionen wie OnPreRender
und OnPostRender
aufrufen zu können.
1 Initialisieren Sie es zunächst mit der Funktion Awake()
.
void Awake()
{
UnityThread.initUnityThread();
}
2 So führen Sie einen Code in der Hauptdatei Thread
eines anderen Threads aus:
UnityThread.executeInUpdate(() =>
{
transform.Rotate(new Vector3(0f, 90f, 0f));
});
Dadurch wird das aktuelle Objekt, an das der Scipt angehängt ist, um 90 Grad gedreht. Sie können jetzt die Unity-API (transform.Rotate
) In einer anderen Thread
verwenden.
3 So rufen Sie eine Funktion im Haupt-Thread Thread
von einem anderen Thread aus auf:
Action rot = Rotate;
UnityThread.executeInUpdate(rot);
void Rotate()
{
transform.Rotate(new Vector3(0f, 90f, 0f));
}
Die # 2 und # 3 Samples werden im Update
Funktion.
4 So führen Sie einen Code in der Funktion LateUpdate
eines anderen Threads aus:
Beispiel hierfür ist ein Kamera-Tracking-Code.
UnityThread.executeInLateUpdate(()=>
{
//Your code camera moving code
});
5 So führen Sie einen Code in der Funktion FixedUpdate
eines anderen Threads aus:
Ein Beispiel dafür, wenn Sie physikalische Aufgaben ausführen, wie das Hinzufügen von Kraft zu Rigidbody
.
UnityThread.executeInFixedUpdate(()=>
{
//Your code physics code
});
6 So starten Sie eine Coroutine-Funktion im Haupt-Thread Thread
von einem anderen Thread aus:
UnityThread.executeCoroutine(myCoroutine());
IEnumerator myCoroutine()
{
Debug.Log("Hello");
yield return new WaitForSeconds(2f);
Debug.Log("Test");
}
Wenn Sie in den Funktionen LateUpdate
und FixedUpdate
nichts ausführen müssen, sollten Sie beide Zeilen dieses Codes auskommentieren unten:
//#define ENABLE_LATEUPDATE_FUNCTION_CALLBACK
//#define ENABLE_FIXEDUPDATE_FUNCTION_CALLBACK
Dies erhöht die Leistung.
Wie?
Die grundlegende Tatsache ist, dass Unity natürlich vollständig rahmenbasiert ist.
Wenn Sie in einem rahmenbasierten System arbeiten, unterscheiden sich Threading-Probleme erheblich von denen in einem herkömmlichen System.
Es ist ein völlig anderes Paradigma.
(Tatsächlich ist es in vielerlei Hinsicht viel einfacher.)
Angenommen, Sie haben ein Unity-Thermometer-Display, das einen bestimmten Wert anzeigt
Thermo.cs
Es wird also eine Funktion haben, die zB in Update aufgerufen wird
func void ShowThermoValue(float fraction) {
display code, animates a thermometer
}
Es ist sehr leicht, diese grundlegende Tatsache zu vergessen .
Klar, es läuft nur auf dem "Hauptthread".
(Es gibt nichts anderes in Unity! Es gibt nur ............... "den Unity-Thread"!)
Das wichtigste Paradigma ist jedoch:es wird einmal pro Frame ausgeführt.
An einer anderen Stelle in Thermo.cs haben Sie jetzt eine Funktion, die das Konzept "Ein neuer Wert ist angekommen" behandelt:
[MonoPInvokeCallback(typeof(ipDel))]
public static void NewValueArrives(float f) {
... ???
}
Beachten Sie, dass natürlich eine Klassenfunktion ist!
Sie können in keiner Weise auf eine normale Unity-Funktion wie ShowThermoValue zugreifen !!!!!! Fußnote 1
Es ist ein völlig bedeutungsloses Konzept. Sie können ShowThermoValue nicht "erreichen".
Alle "Threading-bezogenen" Codebeispiele, die Sie für Unity sehen und die versuchen, "in" einen Thread zu "gelangen", sindvollständig und völlig fehlgeleitet.
Es sind keine Themen zu erreichen!
Wiederum - und das ist überraschend - sind viele der Artikel und Beiträge auf konzeptioneller Ebene völlig inkorrekt, wenn Sie nur das gesamte Schreiben über Threads in Unity im Internet googeln.
Sie könnten verschiedene wissenschaftliche Geräte an ein Rack mit PCs und iPhones anschließen, und neue "Temperatur" -Werte könnten sehr häufig eintreffen .... Dutzende Male pro Frame ... oder vielleicht nur alle paar Sekunden.
Einfacher geht es nicht.
Alles, was Sie aus dem Thread mit den ankommenden Werten tun, ist, darauf zu warten, dass eine Variable in der Komponente festgelegt wird !!
Dies ist eine dieser ungewöhnlichen Situationen:
Ein Großteil des Schreibens auf Threads in Unity ist einfach völlig falsch. Es ist nicht so, als hätte es "einen Fehler" oder "etwas, das korrigiert werden muss". Es ist einfach "völlig und völlig falsch": auf der Ebene des Grundparadigmas.
Überraschenderweise ist der eigentliche Ansatz absurd einfach.
Es ist so einfach, dass Sie vielleicht denken, dass Sie etwas falsch machen.
Also muss die Variable ...
[System.Nonserialized] public float latestValue;
Setze es aus dem "ankommenden Thread" ...
[MonoPInvokeCallback(typeof(ipDel))]
public static void NewValueArrives(float f) {
ThisScript.runningInstance.latestValue = f; // done
}
Ehrlich gesagt ist es das.
Um der weltweit größte Experte für "Threading in Unity" zu sein - was offensichtlich auf Frames basiert -, gibt es nichts weiter zu tun als das oben Genannte.
Und jedes Mal, wenn ShowThermoValue
aufgerufen wird, zeigen Sie einfach diesen Wert an!
Wirklich, das war's!
[System.Nonserialized] public float latestValue;
func void ShowThermoValue() { // note NO arguments here!
display code, animates a thermometer
thermo height = latestValue
}
Sie zeigen einfach den "neuesten" Wert an.
latestValue wurde möglicherweise einmal, zweimal, zehnmal oder hundertmal für diesen Frame festgelegt. Sie zeigen jedoch einfach an, welcher Wert auch immer ist, wenn ShowThermoValue
diesen Frame ausführt !
Was könnten Sie sonst noch anzeigen?
Das Thermometer wird auf dem Bildschirm mit 60 fps aktualisiert. Fußnote 2
Wie der Benutzer @dymanoid hervorgehoben hat (lesen Sie die wichtige Diskussion weiter unten), ist es wichtig, sich daran zu erinnern, dass Float im Unity/C # -Milieu atomar ist, während alles andere (z. B. Vector3 usw.) IS NICHT ATOMISCH. Typischerweise (wie im Beispiel hier) leiten Sie Floats nur aus Berechnungen von beispielsweise nativen Plugins weiter. Aber es ist wichtig zu wissen, dass Vektoren und so weiter NICHT atomar sind.
Manchmal geraten erfahrene Threading-Programmierer in einen Knoten mit einem rahmenbasierten System, weil in einem rahmenbasierten System die meisten Probleme, die durch Probleme mit der Rennbahn und dem Sperren verursacht werden, konzeptionell nicht existieren.
Sie können nicht sinnvoll"mit dem Hauptthread " in Unity, da dieser Hauptthread ............. rahmenbasiert ist!
Abgesehen von Threading-Problemen kann ein kontinuierliches System nicht sinnvoll mit einem Frame-Paradigma-System sprechen.
Die meisten Sperr-, Blockierungs- und Rennstreckenprobleme sind nicht vorhanden im rahmenbasierten Paradigma, weil: wenn Sie latestValue festgelegt haben Zehnmal, eine Million Mal, eine Milliarde Mal in einem bestimmten Rahmen. Was können Sie tun? .. Sie können nur einen Wert anzeigen!
Denken Sie an eine altmodische Plastikfolie. Sie haben buchstäblich nur ... einen Rahmen, und das war's. Wenn Sie in einem bestimmten Frame eine Billion Mal den neuesten Wert eingestellt haben, zeigt ShowThermoValue einfach (für diese 60stel Sekunde) den einen Wert an, den es beim Ausführen erfasst.
Alles, was Sie tun, ist: Lassen Sie Informationen irgendwo, die das Frame-Paradigma-System verwenden wird, wenn es möchte .
Das ist alles auf den Punkt gebracht.
Daher verschwinden die meisten "Threading-Probleme"in Unity.
Alles, was Sie tun könnenvon
andere Berechnungsthreads oder
von Plugin-Threads,
ist nur "Drop-Off-Werte", die das Spiel verwenden kann.
Das ist es!
Fußnoten
1 Wie konntest du? Vergessen Sie als Gedankenexperiment das Problem, dass Sie sich in einem anderen Thread befinden. ShowThermoValue wird einmal pro Frame von der Frame-Engine ausgeführt. Sie können es nicht auf sinnvolle Weise "nennen". Anders als in normaler OO Software können Sie eine Instanz der Klasse (eine Komponente ohne Bedeutung) nicht instanziieren und diese Funktion ausführen - das ist völlig ohne Bedeutung.
Bei der "normalen" Thread-Programmierung können Threads zurück und vor und so weiter sprechen, und dabei haben Sie Bedenken hinsichtlich des Sperrens, der Rennstrecke und so weiter. Aber das ist alles bedeutungslos in einem ECS-Frame-basierten System. Es gibt nichts zu "reden".
Nehmen wir an, dass Unity tatsächlich Multithreaded war !!!! Die Unity-Leute haben also den ganzen Motor in Multithread-Manier laufen lassen.Es würde keinen Unterschied machen- Sie können ShowThermoValue nicht in irgendeiner sinnvollen Weise aufrufen! Es ist eine Komponente, die die Frame-Engineeinmal pro Frame ausführtund das war's.
Also NewValueArrivesist nirgendwo - es ist eine Klassenfunktion!
Beantworten wir die Frage in der Überschrift:
Msgstr "Unity API von einem anderen Thread verwenden oder eine Funktion im Hauptthread aufrufen?"
Das Konzept ist >> völlig bedeutungslos <<. Unity basiert (wie alle Game Engines) auf Frames. Es gibt kein Konzept, eine Funktion im Haupt-Thread "aufzurufen". Um eine Analogie zu ziehen: Es wäre wie bei einem Kameramann in der Zeit des Zelluloidfilms, der fragt, wie man etwas tatsächlichaufeinem der Bilder "bewegt".
Das ist natürlich sinnlos. Sie können lediglich etwas für das nächste Foto, das nächste Bild, ändern.
2 Ich beziehe mich auf den "Arrival-Values-Thread" ... in der Tat! NewValueArrives kann auf dem Hauptthread ausgeführt werden oder nicht !!!! Es kann auf dem Thread des Plugins oder auf einem anderen Thread laufen! Es kann tatsächlich zu dem Zeitpunkt, zu dem Sie den NewValueArrives-Aufruf bearbeiten, vollständig single-threaded sein!Es spielt einfach keine Rolle!In einem rahmenbasierten Paradigma können Sie nur die Informationen "herumliegen lassen", die Komponenten wie ShowThermoValue kann nach Belieben verwendet werden.
Ich habe diese Lösung für dieses Problem verwendet. Erstellen Sie ein Skript mit diesem Code und hängen Sie es an ein Spielobjekt an:
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();
}
}
}
Wenn Sie dann etwas im Haupt-Thread aufrufen und von einer anderen Funktion in Ihrer Anwendung auf die Unity-API zugreifen müssen:
ExecuteOnMainThread.RunOnMainThread.Enqueue(() => {
// Code here will be called in the main thread...
});