2016-12-26 20 views
6

Mój problem polega na tym, że próbuję użyć gniazda Unity, aby coś zaimplementować. Za każdym razem, gdy otrzymuję nową wiadomość, muszę zaktualizować ją do updattext (jest to tekst Unity). Kiedy jednak wykonuję poniższy kod, aktualizacja void nie wywołuje za każdym razem.Użyj Unity API z innego wątku lub wywołaj funkcję w głównym wątku

Powodem nie obejmują updatetext.GetComponent<Text>().text = "From server: "+tempMesg; w pustce getInformation jest ta funkcja jest w wątku, kiedy to, że w getInformation() będzie pochodzić z błędem:

getcomponentfastpath can only be called from the main thread

Myślę, że problem czy nie wiem, jak uruchomić główny wątek i wątek podrzędny w języku C# razem? Lub nie może inne problemy ... nadzieję, że ktoś może pomóc .. Jest to mój kod:

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

Odpowiedz

0

Powodem tego wyjątku jest UnityEngine nie jest bezpieczeństwo wątków. zamiast używać współprogram wątku:

void Start() { 
    updatetext.GetComponent<Text>().text = "Waiting..."; 
    clientSocket.Connect("10.132.198.29", 8888); 
    StartCoroutine(getInformation()); 
    Debug.Log ("Running the client"); 
} 

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

IEnumerator getInformation(){ 
    while (true) { 
     //... 
     yield return null; 
     if(done) break;//terminate condition 
    } 
} 

umieścić swój kod przed //... i ustawić logiczną done wskazać, gdy informacja jest gotowy i metoda powinna zakończyć.

+0

Cześć Bijan, nie ma żadnej różnicy między wywołaniem funkcji sieciowej w normalnej funkcji Jedność kontra coroutine. OP będzie nadal miał problemy z blokowaniem (http://stackoverflow.com/q/41207487/3785314), jeśli wątek lub asynchronizacja nie jest używany. Wyjaśnię to bardziej w odpowiedzi. – Programmer

+0

możesz sprawdzić moją odpowiedź, aby zobaczyć, o czym mówię. Używanie programu coroutine będzie nadal blokować aplikację podczas nawiązywania połączenia lub odbierania z serwera. Używasz tylko elementów sieciowych z coroutine, jeśli jest to API Unity, taki jak 'WWW' lub' UnityWebRequest', który obsługuje wydajność coroutine. Gniazdo C# nie. Musisz użyć funkcji 'Thread' lub' Async', takich jak 'BeginAccept' i' BeginReceive'. – Programmer

+1

@Programmer to jest cenna informacja. dziękuję – Bijan

24

Jedność nie jest bezpieczna Thread, więc zdecydowali uniemożliwić wywoływanie swojego API z innego Thread przez dodanie mechanizmu wyrzucania wyjątku, gdy jego interfejs API jest używany z innego Thread.

To pytanie było zadawane tak wiele razy, ale nie było żadnego właściwego rozwiązania/odpowiedzi na żadną z nich. Odpowiedzi są zwykle "użyj wtyczki" lub zrób coś, co nie jest bezpieczne dla wątków. Mam nadzieję, że to będzie ostatni.

Rozwiązaniem, które zwykle można zobaczyć na stronie internetowej Stackoverflow lub Unity jest użycie zmiennej boolean, aby główny wątek wiedział, że musisz wykonać kod w głównej wersji Thread. To nie jest w porządku, ponieważ nie jest nici bezpieczne i nie daje kontroli, aby zapewnić, która funkcja zadzwonić. Co jeśli masz kilka numerów Threads, które muszą powiadomić główny wątek?

Innym rozwiązaniem, które można zobaczyć, jest użycie coroutine zamiast Thread. To działa , a nie. Używanie coroutine do gniazd niczego nie zmieni. Nadal będziesz mieć problemy z numerem freezing. Musisz trzymać się swojego kodu Thread lub użyć Async.

Jednym z odpowiednich sposobów, aby to zrobić, jest utworzenie kolekcji, takiej jak List. Jeśli potrzebujesz czegoś do wykonania w głównym wątku, wywołaj funkcję przechowującą kod do wykonania w Action. Zrozumiałem List z Action do lokalnego List z Action następnie wykonać kod z lokalnego Action w tym List wtedy jasne, że List. Zapobiega to konieczności czekania na zakończenie wykonywania przez użytkownika Threads.

Musisz również dodać volatile boolean, aby powiadomić o funkcji Update, czy jest kod oczekujący w List do wykonania.Podczas kopiowania pliku List do lokalnego List należy go owinąć wokół słowa kluczowego lock, aby zapobiec zapisywaniu w nim innego wątku.

skrypt, który wykonuje to, co wspomniałem powyżej:

UnityThread Scenariusz:

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

ZASTOSOWANIE:

Ta implementacja pozwala na wywołanie funkcji w najbardziej używane funkcje Unity: Update, LateUpdate a nd FixedUpdate funkcji. Pozwala to również wywołać uruchomienie funkcji coroutine w głównej wersji Thread. Można go rozszerzyć, aby móc wywoływać funkcje w innych funkcjach oddzwaniania Unity, takich jak OnPreRender i OnPostRender.

. Najpierw zainicjuj go za pomocą funkcji Awake().

void Awake() 
{ 
    UnityThread.initUnityThread(); 
} 

.Aby wykonanie kodu w głównym Thread innej nici:

UnityThread.executeInUpdate(() => 
{ 
    transform.Rotate(new Vector3(0f, 90f, 0f)); 
}); 

ten obraca się aktualny obiekt skrypcie jest dołączone do 90 ° C. Możesz teraz używać Unity API (transform.Rotate) w innym Thread.

.Aby wywołać funkcję w głównej Thread z innego wątku:

Action rot = Rotate; 
UnityThread.executeInUpdate(rot); 


void Rotate() 
{ 
    transform.Rotate(new Vector3(0f, 90f, 0f)); 
} 

# 2 i 3 Nr próbki wykonuje się w funkcji Update.

.Aby wykonanie kodu w funkcji LateUpdate innej nici:

Przykład ten jest kodem śledzenia kamery.

UnityThread.executeInLateUpdate(()=> 
{ 
    //Your code camera moving code 
}); 

.Aby wykonać kod w funkcji FixedUpdate z innego wątku:

przykład tego, gdy robi fizyki rzeczy takie jak dodanie siły Rigidbody.

UnityThread.executeInFixedUpdate(()=> 
{ 
    //Your code physics code 
}); 

.Aby uruchomić funkcję współprogram w głównym Thread z innego wątku:

UnityThread.executeCoroutine(myCoroutine()); 

IEnumerator myCoroutine() 
{ 
    Debug.Log("Hello"); 
    yield return new WaitForSeconds(2f); 
    Debug.Log("Test"); 
} 

Wreszcie, jeśli nie trzeba wykonać coś w funkcjach LateUpdate i FixedUpdate należy komentować obu linii tego kodu poniżej:

//#define ENABLE_LATEUPDATE_FUNCTION_CALLBACK 
//#define ENABLE_FIXEDUPDATE_FUNCTION_CALLBACK 

Spowoduje to zwiększenie wydajności.

+0

Przepraszam ... Próbuję wdrożyć twoje rozwiązanie ... Ale kiedy wpisuję UnityThread.initUnityThread(); pokazuje błąd, że "UnityThread" nie istnieje w bieżącym kontekście. Przepraszam za nowe w jedności ... Czy mógłbyś objaśnić swój kod w bardziej szczegółowy sposób? ... Wielkie dzięki .. – user6142261

+0

Musisz stworzyć skrypt o nazwie "UnityThread", wtedy musisz skopiować kod 'UnityThread' w mojej odpowiedzi na to pytanie. Proszę powiedz mi, która część tego jest trudna? – Programmer

+0

'executeCoroutine' musi znajdować się wewnątrz' #if (ENABLE_UPDATE_FUNCTION_CALLBACK) 'albo otrzymasz błąd kompilatora w linii' executeInUpdate (() => instance.StartCoroutine (action)); 'linii, gdy symbol nie jest określone. –

Powiązane problemy