在做畢設的時候涉及到了較大數據的讀取,每次從硬盤讀都會卡很久,於是找資料之后自己做了個簡單的多線程解決方案。
一共有兩個類。第一個類ThreadJob如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using System.Threading;
public class ThreadJob {
public bool isDone { get {
int val = 1;
Interlocked.CompareExchange(ref val,0, _isDone);
if (val == 0)
return true;
return false;
}
set {
_isDone = value ? 1 : 0;
}
}
private int _isDone;
protected Thread thread;
public void Start() {
thread = new Thread(Run);
thread.IsBackground = true;
thread.Start();
}
private void Run() {
ThreadFunction();
isDone = true;
}
protected virtual void ThreadFunction() {
}
public IEnumerator WaitTillDone() {
while (!isDone)
yield return null;
}
}
注意的幾點:
0. 通過繼承ThreadJob,override ThreadFunction()來實現自己的線程。
- 主線程直接用構造函數構造一個ThreadJob,然后調用Start()開始運行。自己不斷檢查isDone來查看線程是否完成。
- isDone里使用了.net的原子操作,我不清楚這種寫法是否最優。當然也可以直接用lock。
- isBackground保證線程會隨着主線程的退出而退出。否則主線程退出后該線程不會結束。
- WaitTillDone()是一個方便主線程檢查isDone的函數。具體使用見后文。
第二個類是ThreadManager,主要用途是子線程向主線程發消息。(比如讀取文件的進度等)。因為unity的.net版本沒有concurrent容器,這里用的是lock給隊列上鎖。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
public class ThreadManager : MonoBehaviour {
public static ThreadManager instance {
get {
if (_instance == null) {
_instance = FindObjectOfType<ThreadManager>();
}
return _instance;
}
}
private static ThreadManager _instance;
private void Awake() {
_instance = this; //if the first call is used in a thread, it will throw a exception, since sub-thread can't use FindObjectOfType
}
private Queue<Action> _callbackQueue = new Queue<Action>();
public void AddThreadCallback(Action callback) {
lock (_callbackQueue) {
_callbackQueue.Enqueue(callback);
}
}
// Update is called once per frame
void Update () {
lock (_callbackQueue) {
while (_callbackQueue.Count > 0) {
_callbackQueue.Dequeue()();
}
}
}
}
要注意的是盡管在instance屬性里有FindObjectOfType,但是因為子線程無法使用Unity的函數,所以還是需要在Awake里手動賦值一下。否則如果子線程首先調用了instance屬性就會報錯。
使用的時候在子線程里調用AddThreadCallback即可。在主線程的下一幀就會調用。
具體使用例子:
public void ImportTexture() {
var thread = GetReadTextureThread();
thread.Start();
StartCoroutine(ReadMain(thread));
}
private IEnumerator ReadMain(ThreadedReadTexture thread) {
yield return thread.WaitTillDone(); //注意這里的用法
var info = thread.GetTexture();
_tex = new Texture3D(info.width, info.height, info.thickness, TextureFormat.RFloat, false);
_tex.SetPixels(info.data);
_tex.Apply();
}
用Threadmanager進行通信,Notify函數由子線程調用:
public void Notify(string progress) {
ThreadManager.instance.AddThreadCallback(
() => {
SystemController.instance.hint.hintText = progress; //顯示一條消息
});
}