在Unity中,子線程是無法調用Unity主線程的API的,因為unity不允許這麼幹。
但是我們可以通過别的途徑,實作這一功能。
大緻思路:
将子線程中需要調用的函數,通過委托傳遞給Loom中的委托清單,在Loom中去調用該委托。因為Loom是繼承MonoBehavior的,挂載在空物體上面,是以由他去執行委托,自熱是沒問題的!
詳細思路:
- 一開始在Unity中建立一個新物體obj,挂上Loom腳本。
- Loom中有List<Action> listActions;
- 将子線程中的函數,以委托的形式,傳遞給listActions,Loom在Updata裡面,輪詢去調用委托。
- 巧妙的将子線程無法調用主線程的API 轉化為 子線程傳遞給空物體上面的Loom,在Loom裡面去調用委托來調用主線程API。
Loom.cs:
using UnityEngine;
using System.Collections.Generic;
using System;
using System.Threading;
using System.Linq;
public class Loom : MonoBehaviour
{
//是否已經初始化
static bool isInitialized;
private static Loom _ins;
public static Loom ins { get { Initialize(); return _ins; } }
void Awake()
{
_ins = this;
isInitialized = true;
}
//初始化
public static void Initialize()
{
if (!isInitialized)
{
if (!Application.isPlaying)
return;
isInitialized = true;
var obj = new GameObject("Loom");
_ins = obj.AddComponent<Loom>();
DontDestroyOnLoad(obj);
}
}
//單個執行單元(無延遲)
struct NoDelayedQueueItem
{
public Action<object> action;
public object param;
}
//全部執行清單(無延遲)
List<NoDelayedQueueItem> listNoDelayActions = new List<NoDelayedQueueItem>();
//單個執行單元(有延遲)
struct DelayedQueueItem
{
public Action<object> action;
public object param;
public float time;
}
//全部執行清單(有延遲)
List<DelayedQueueItem> listDelayedActions = new List<DelayedQueueItem>();
//加入到主線程執行隊列(無延遲)
public static void QueueOnMainThread(Action<object> taction, object param)
{
QueueOnMainThread(taction, param, 0f);
}
//加入到主線程執行隊列(有延遲)
public static void QueueOnMainThread(Action<object> action, object param, float time)
{
if (time != 0)
{
lock (ins.listDelayedActions)
{
ins.listDelayedActions.Add(new DelayedQueueItem { time = Time.time + time, action = action, param = param });
}
}
else
{
lock (ins.listNoDelayActions)
{
ins.listNoDelayActions.Add(new NoDelayedQueueItem { action = action, param = param });
}
}
}
//目前執行的無延時函數鍊
List<NoDelayedQueueItem> currentActions = new List<NoDelayedQueueItem>();
//目前執行的有延時函數鍊
List<DelayedQueueItem> currentDelayed = new List<DelayedQueueItem>();
void Update()
{
if (listNoDelayActions.Count > 0)
{
lock (listNoDelayActions)
{
currentActions.Clear();
currentActions.AddRange(listNoDelayActions);
listNoDelayActions.Clear();
}
for (int i = 0; i < currentActions.Count; i++)
{
currentActions[i].action(currentActions[i].param);
}
}
if (listDelayedActions.Count > 0)
{
lock (listDelayedActions)
{
currentDelayed.Clear();
currentDelayed.AddRange(listDelayedActions.Where(d => Time.time >= d.time ));
for (int i = 0; i < currentDelayed.Count; i++)
{
listDelayedActions.Remove(currentDelayed[i]);
}
}
for (int i = 0; i < currentDelayed.Count; i++)
{
currentDelayed[i].action(currentDelayed[i].param);
}
}
}
void OnDisable()
{
if (_ins == this)
{
_ins = null;
}
}
}
測試
Text text;
void Start()
{
Thread recvThread = new Thread( ThreadFun ) { IsBackground = true };
recvThread.Start();
}
void ThreadFun()
{
// 用Loom的方法在Unity主線程中調用Text元件
Loom.QueueOnMainThread((param) =>
{
text.text = "測試";
}, null);
}
需要注意的是,應當提前把Loom執行個體化出來。
其實在上述的測試代碼中,在ThreadFun()中調用Loom函數,Loom已經執行了初始化了。
假如這是第一次調用Loom,那麼會觸發Loom的初始化函數Initialize(),會執行 new GameObject("Loom"),相當于還是在子線程中調用主線程代碼,還是行不通 會報錯的。是以我們要提前給Loom執行個體化。
方法有2:
void Start()
{
Loom.Initialize();
}