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.
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
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
@Programmer to jest cenna informacja. dziękuję – Bijan