[Unity]多線程編程的一點心得


在做畢設的時候涉及到了較大數據的讀取,每次從硬盤讀都會卡很久,於是找資料之后自己做了個簡單的多線程解決方案。

一共有兩個類。第一個類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()來實現自己的線程。

  1. 主線程直接用構造函數構造一個ThreadJob,然后調用Start()開始運行。自己不斷檢查isDone來查看線程是否完成。
  2. isDone里使用了.net的原子操作,我不清楚這種寫法是否最優。當然也可以直接用lock。
  3. isBackground保證線程會隨着主線程的退出而退出。否則主線程退出后該線程不會結束。
  4. 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;    //顯示一條消息
            });
    }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM