Ответ 1
Unity не является Thread
безопасным, поэтому они решили сделать невозможным вызов своего API из другого Thread
путем добавления механизма для исключения исключения, когда его API используется из другого Thread
.
Этот вопрос задавался так много раз, но не было правильного решения/ответа на любой из них. Ответы обычно "используют плагин" или делают что-то не потокобезопасное. Надеюсь, это будет последний.
Решение, которое вы обычно видите на веб-сайте Stackoverflow или Unity, - это просто использовать переменную boolean
, чтобы основной поток знал, что вам нужно выполнить код в главном Thread
. Это неверно, поскольку оно не является потокобезопасным и не дает вам контроля над тем, какую функцию вызывать. Что делать, если у вас есть несколько Threads
, которые должны уведомить основной поток?
Другим решением, которое вы увидите, является использование сопрограммы вместо Thread
. Это работает не. Использование сопрограммы для сокетов ничего не изменит. Вы все равно столкнетесь с проблемами замораживания. Вы должны придерживаться своего кода Thread
или использовать Async
.
Один из правильных способов сделать это - создать коллекцию, такую как List
. Когда вам нужно что-то выполнить в главном потоке, вызовите функцию, которая хранит код для выполнения в Action
. Скопируйте List
из Action
в локальный List
из Action
, затем выполните код из локального Action
в этом List
, затем очистите это List
. Это препятствует тому, чтобы другой Threads
мог дождаться завершения его выполнения.
Вам также нужно добавить volatile boolean
, чтобы уведомить функцию Update
, что в List
есть код, который должен быть выполнен. При копировании List
в локальный List
он должен быть обернут вокруг ключевого слова lock
, чтобы предотвратить запись другого потока.
A script, который выполняет то, что я упомянул выше:
UnityThread
Script:
#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;
}
}
}
ИСПОЛЬЗОВАНИЕ
Эта реализация позволяет вам вызывать функции в 3 наиболее используемых функциях Unity: Update
, LateUpdate
и FixedUpdate
. Это также позволяет вам вызывать функцию сопрограммы в главном Thread
. Он может быть расширен, чтобы иметь возможность вызывать функции в других функциях обратного вызова Unity, таких как OnPreRender
и OnPostRender
.
1. Сначала инициализируйте его из функции Awake()
.
void Awake()
{
UnityThread.initUnityThread();
}
2. Чтобы выполнить код в главном Thread
из другого потока:
UnityThread.executeInUpdate(() =>
{
transform.Rotate(new Vector3(0f, 90f, 0f));
});
Это приведет к вращению текущего объекта, к которому привязан склип, до 90 град. Теперь вы можете использовать Unity API (transform.Rotate
) в другом Thread
.
3. Чтобы вызвать функцию в главном Thread
из другого потока:
Action rot = Rotate;
UnityThread.executeInUpdate(rot);
void Rotate()
{
transform.Rotate(new Vector3(0f, 90f, 0f));
}
Образцы # 2 и # 3 выполняются в Update
.
4. Чтобы выполнить код в LateUpdate
из другого потока:
Пример этого - код отслеживания камеры.
UnityThread.executeInLateUpdate(()=>
{
//Your code camera moving code
});
5. Чтобы выполнить код в FixedUpdate
из другого потока:
Пример этого при выполнении физических упражнений, таких как добавление силы к Rigidbody
.
UnityThread.executeInFixedUpdate(()=>
{
//Your code physics code
});
6. Чтобы запустить функцию сопрограммы в главном Thread
из другого потока:
UnityThread.executeCoroutine(myCoroutine());
IEnumerator myCoroutine()
{
Debug.Log("Hello");
yield return new WaitForSeconds(2f);
Debug.Log("Test");
}
Наконец, если вам не нужно ничего выполнять в LateUpdate
и FixedUpdate
, вы должны прокомментировать обе строки этого кода ниже:
//#define ENABLE_LATEUPDATE_FUNCTION_CALLBACK
//#define ENABLE_FIXEDUPDATE_FUNCTION_CALLBACK
Это повысит производительность.