Stackoverflow原文链接
Unity是基于框架开发的,因此Unity是线程不安全的。Unity里有一个机制,当其他进程访问Unity API时,会抛出一个异常,这就导致其他进程无法直接访问Unity API。举例来说,要控制一个物体的移动旋转,只能在主线程中操作,子线程没有办法使用GetComponent这样的API,但是子线程可以计算位移、旋转角度(即子线程可以使用Unity中的基本数据结构int、Vector3、Quaternion等)。
在Stackoverflow或Unity论坛网站上,通常会看到的解决方案是使用一个布尔变量来让主线线程知道你需要在主线线程中执行代码。这样做是不对的,因为它不是线程安全的,并且不能控制调用哪个函数。如果有多个线程需要通知主线程怎么办?
另一种解决方案是使用协同程序(StartCoroutine)而不是线程。这是行不通的。对Socket来说,使用协程不会改变任何东西。还是要使用线程代码或使用异步。
一个正确的方法时创建一个集合(比如List),当你需要在主线程中执行某些操作时,调用那些存储在Action(委托)中的方法。将委托列表复制到本地,然后从本地委托列表执行方法,执行后从列表中清除,这样可以防止其他线程一直在等待,造成阻塞。
还需要添加一个bool变量,以通知Update函数,在列表中有代码等待执行。当将该列表复制到一个本地列表时,应该将其封装在lock关键字周围,以防止其他线程写入该列表,保证线程安全。
UnityThread 脚本:
#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函数。还允许我们在主线程中调用运行协同程序函数。它甚至可以被扩展到能够调用其他Unity回调函数如OnPreRender和OnPostRender。
1.首先,从Awake()函数初始化它
void Awake()
{
UnityThread.initUnityThread();
}
2.从另一个线程执行主线程中的代码:
UnityThread.executeInUpdate(() =>
{
transform.Rotate(new Vector3(0f, 90f, 0f));
});
这会将该脚本的当前对象旋转90°
3.从另一个线程调用主线程中的函数:
Action rot = Rotate;
UnityThread.executeInUpdate(rot);
void Rotate()
{
transform.Rotate(new Vector3(0f, 90f, 0f));
}
#2和#3示例在更新函数中执行。
4.从另一个线程执行LateUpdate函数中的代码:
UnityThread.executeInLateUpdate(()=>
{
//Your code
});
5.从另一个线程执行FixedUpdate函数中的代码:
UnityThread.executeInFixedUpdate(()=>
{
//Your code
});
6.从另一个线程在主线程中启动一个协同函数:
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
这将提高性能。