• Unity 中 使用c#线程


    使用条件

      天下没有免费的午餐,在我使用unity的那一刻,我就感觉到不自在,因为开源所以不知道底层实现,如果只是简单的做点简单游戏,那就无所谓的了,但真正用到实际地方的时候,就会发现一个挨着一个坑,然后你就跟着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 ,看到这个地方之后我的第一个思路代码如下:

    image

    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 ,看代码:

    队列:

    image

    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中原先的逻辑不需要更该,就可以看到测试中按键点击后,物体的旋转。

  • 相关阅读:
    第24课 多线程开发
    第23课 装饰器
    第22课 调用外部程序
    第20课 异常处理
    第19课 习题讲解
    第18课 面向对象
    第17课 调试程序
    第16课 pycharm 使用
    第15课 模块与包
    第14课 再识函数
  • 原文地址:https://www.cnblogs.com/zsb517/p/4029521.html
Copyright © 2020-2023  润新知