[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;    //显示一条消息
            });
    }
posted @ 2017-05-03 16:55  yangrc1234  阅读(5912)  评论(1编辑  收藏  举报