0评论

Unity 中使用C#线程

文章来自https://www.cnblogs.com/zsb517/p/4029521.html 2019-03-15 105浏览

想免费获取内部独家PPT资料库?观看行业大牛直播?点击加入腾讯游戏学院游戏开发行业精英群711501594

对于开源程序来说,大家不知道的是其底层实现,如果只是简单的做点简单游戏,那就无所谓的了,但真正用到项目开发中,就会遇到各种各样的问题,然后你就跟着unity做各种妥协。如果开发中需要使用网络等等涉及到多线程的地方,就会用到c#的多线程,注意不是unity的协程,你要做的妥协参考下面:
  1. 变量(都能指向相同的内存地址)都是共享的
  2. 不是UnityEngine的API能在分线程运行
  3. UnityEngine定义的基本结构(int,float,Struct定义的数据类型)可以在分线程计算,如 Vector3(Struct)可以 , 但Texture2d(class,根父类为Object)不可以。
  4. UnityEngine定义的基本类型的函数可以在分线程运行,类的函数不能在分线程运行

Unity设计之初应该就是一个单线程,不允许在另外的线程中进行渲染等等的工作可以理解,不然又要增加很多机制去处理这个问题,会给新来的人徒增很多烦恼,比如说我:
//class
public class NIDataManager: MonoBehaviour
{
    public static NIDataManager GetInstance()
    {
    }
}
/*thread*/
    private void RequestEvevtThread()
    {
        while (!m_IsExitThread)
        {
            /*wait set event*/
            m_EventWait.WaitOne(-1);
            /*reference SendRequestToService,error ,using subclass of MonoBehaviour in thread*/
            if (NIDataManager.GetInstance().isServiceOpen())  
            {
                if (0 == ServiceInterface.sendRequest((int)(m_EventParam.ActionType), m_EventParam.DurationTime, ref m_EventParam.Response))
                {
                    if (m_EventParam.DelegateCallback != null)
                    {
                        m_EventParam.DelegateCallback(m_EventParam.Response);
                    }
                }
                else
                {
                    // Debug.Log("sendRequest false");
                    continue;
                }
            }
            m_isProcessing = false;
        }
    }

如果你也这样干,恭喜你,会得到错误:CompareBaseObjectsInternal can only be called from the main thread.

关于这个错误的一些分析可以参考:http://forum.unity3d.com/threads/comparebaseobjectsinternal-error.184069/

委托是个坑

第一版思路

游戏通过向体感引擎申请动作识别结果,但是这个结果在处理结束之前,需要堵塞线程,交互设计本身是有问题的,如果在主线程做肯定不可能。OK ,一般情况下,游戏中角色申请完事件之后需要根据结果对人物的状态,如位置等等的进行修改,这其中肯定会涉及到unity的API ,看到这个地方之后我的第一个思路代码如下:

using System;
using System.Collections.Generic;
using System.Threading;
public class NIEventThread
{
    /*事件处理后的委托*/
    public delegate void EventCallbackMethod(int bSuccess);
    /*Event申请使用的参数*/
    public struct stEventParam
    {
        public int ActionType;                //动作类型
        public int DurationTime;              //限定时间
        public EventCallbackMethod DelegateCallback;  //处理后的结果返回
        public stEventParam(int type, int time, EventCallbackMethod func)
        {
            ActionType = type;
            DurationTime = time;
            DelegateCallback = func;
        }
    }
    /*线程中申请需要的参数*/
    private stEventParam m_EventParam = new stEventParam();
    /*线程控制量,等待申请事件*/
    private AutoResetEvent m_WaitEvent = new AutoResetEvent(false);
    /*线程结束控制*/
    private bool m_IsExitThread = false;
    /*线程,需要设置为后台线程*/
    private Thread m_EventThread = null;
    public Thread GetThread
    {
        get { return m_EventThread; }
    }
    /*初始化的时候就打开线程*/
    public NIEventThread()
    {
        StartThread();
    }
    ~NIEventThread()
    {
        EndThread();
    }
    /*创建和开启线程*/
    private void StartThread()
    {
        m_EventThread = new Thread(this.RequestEvevtThread);
        m_EventThread.IsBackground = true;
        m_EventThread.Start();
    }
    /*结束线程*/
    private void EndThread()
    {
        m_IsExitThread = false;
    }
    /* 请求事件*/
    public void RequestEvent(stEventParam param)
    {
        m_EventParam = param;
        m_WaitEvent.Set();
    }
    /*线程处理方法*/
    public void RequestEvevtThread()
    {
        while (!m_IsExitThread)
        {
            /*等待事件激活*/
            m_WaitEvent.WaitOne(-1);
            Console.WriteLine("等你好久了" + "duration:" + m_EventParam.DurationTime);
            if (m_EventParam.DelegateCallback != null)
            {
                m_EventParam.DelegateCallback(1);
            }
        }
    }
}

Unity中的调用:
// Update is called once per frame
    void Update ()
    {
        if (Input.GetKey(KeyCode.Space))
        {
            //申请事件
            t.RequestEvent(new stEventParam(3, 256,test));
        }
    }
    void test(int t)
    {
        //掉用unity API
        transform.Rotate(new Vector3(30,0,0));
    }

处理结果:
产生错误“InternalGetTransform can only be called from the main thread.”

也就是说:这个委托还在分支线程中执行,而不是在代码所在的主线程中执行,具体原因另外一篇文章讲解,其实在c#中控件也不允许在另外一个线程中进行控制,但是微软提供了方法,unity没提供或者说本身就不建议这么干。

怎么破?我这里给出我自己的思路,增加队列,在时间请求线程中先队列中增加要处理的事件,在unity主线程中取队列中的参数进行处理,OK ,看代码:

队列:

public sealed class RequestMessageQueue
{
    private readonly static RequestMessageQueue  mInstance = new RequestMessageQueue();
    public static RequestMessageQueue GetInstance()
    {
        return mInstance;
    }
    public  void EnQueue(stEventResult st)
    {
        m_Event.Enqueue(st);
    }
    public stEventResult DeQueue()
    {
        return m_Event.Dequeue();
    }
    public int Count
    {
        get { return m_Event.Count; }
    }
    private RequestMessageQueue()
    {
        m_Event = new Queue<stEventResult>() ;
    }
    Queue<stEventResult> m_Event;
}

unity中事件处理
public class RequestMessageHandle : MonoBehaviour
{
    // Use this for initialization
    void Start () 
    {
    }
    // Update is called once per frame
    void Update () 
    {
        while (RequestMessageQueue.GetInstance().Count != 0)
        {
            stEventResult st = RequestMessageQueue.GetInstance().DeQueue();
            st.DelegateCallback(st.result);
        }
    }
}

再来看看线程中调用:
/*线程处理方法*/
    public void RequestEvevtThread()
    {
        while (!m_IsExitThread)
        {
            /*等待事件激活*/
            m_WaitEvent.WaitOne(-1);
            Console.WriteLine("等你好久了" + "duration:" + m_EventParam.DurationTime);
            if (m_EventParam.DelegateCallback != null)
            {
                //m_EventParam.DelegateCallback(1);
                 RequestMessageQueue.GetInstance().EnQueue(new stEventResult(m_EventParam.ActionType,1,m_EventParam.DelegateCallback));
            }
        }
    }

修改完之后,unity中原先的逻辑不需要更该,就可以看到测试中按键点击后,物体的旋转。

总结

结合这篇文章的作者在使用C#线程的情况来看,调用线程的时候需要注意的地方有很多,大家在使用其代码部分的时候需要注意下。