Unity的Socket網絡程式設計中,為了防止程式卡死,一般使用多線程來監聽端口,當收到來自用戶端的消息時,需要顯示在界面上。但是如果直接在子線程中操作Unity的界面或物體會報錯。國外一個大神寫了一個UnityThread類很好地解決了這個問題。
紫色文字為谷歌翻譯後的原文,個别地方作了修改。
Unity是Thread不安全的,是以他們決定Thread通過添加一種機制在從另一個人使用其API時引發異常來使其無法從另一個人調用API Thread。
這個問題已經被問了很多遍了,但是沒有一個合适的解決方案/答案。答案通常是“使用插件”或執行某些不是線程安全的操作。希望這将是最後一個。
您通常會在Stackoverflow或Unity論壇網站上看到的解決方案是簡單地使用一個boolean變量讓主線程知道您需要在main中執行代碼Thread。這是不正确的,因為它不是線程安全的,并且無法控制您提供要調用的函數。如果您有多個Threads需要通知主線程怎麼辦?
您将看到的另一種解決方案是使用協程而不是Thread。這并不能正常工作。對套接字使用協程不會更改任何内容。您仍然會遇到當機問題。您必須堅持自己的Thread代碼或使用Async。
執行此操作的正确方法之一是建立一個集合,例如List。當您需要在主線程中執行某些操作時,調用一個函數,該函數存儲要在中執行的代碼Action。複制List的Action到本地List的Action,然後從本地執行的代碼Action在List随後清除List。這樣可以避免其他人Threads等待它完成執行。
您還需要添加一個,volatile boolean以通知Update函數中有等待List執行的代碼。将拷貝List到localList時,應将其包裝在lock關鍵字周圍,以防止另一個線程對其進行寫入。
UnityThread用法:
這種實作允許你調用的函數3個最常用的統一功能:Update,LateUpdate和FixedUpdate功能。這也允許您在主線程中調用運作協程函數。它可以擴充為能夠調用其他Unity回調函數中的函數,例如OnPreRender和OnPostRender。
1。首先,通過Awake()函數對其進行初始化。
void Awake()
{
UnityThread.initUnityThread();
}
2。在子線程中執行主線程中Update函數的代碼,請執行以下操作:
UnityThread.executeInUpdate(() =>
{
transform.Rotate(new Vector3(0f, 90f, 0f));
});
這會将scipt所連接配接的目前對象旋轉到90度。您現在可以transform.Rotate在另一個中使用Unity API()Thread。
3。在子線程中執行主線程中一個函數,請執行以下操作:
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。在子線程中啟動主線程協程函數:
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
這将提高性能。
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;
}
}
}
附上原文連結