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中原先的邏輯不需要更該,就可以看到測試中按鍵點擊后,物體的旋轉。


免責聲明!

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



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