天天看點

UnityThread子線程使用隻能在主線程中調用的函數或Unity API

Unity的Socket網絡程式設計中,為了防止程式卡死,一般使用多線程來監聽端口,當收到來自用戶端的消息時,需要顯示在界面上。但是如果直接在子線程中操作Unity的界面或物體會報錯。國外一個大神寫了一個UnityThread類很好地解決了這個問題。

紫色文字為谷歌翻譯後的原文,個别地方作了修改。

Unity是Thread不安全的,是以他們決定Thread通過添加一種機制在從另一個人使用其API時引發異常來使其無法從另一個人調用API Thread。

這個問題已經被問了很多遍了,但是沒有一個合适的解決方案/答案。答案通常是“使用插件”或執行某些不是線程安全的操作。希望這将是最後一個。

您通常會在Stackoverflow或Unity論壇網站上看到的解決方案是簡單地使用一個boolean變量讓主線程知道您需要在main中執行代碼Thread。這是不正确的,因為它不是線程安全的,并且無法控制您提供要調用的函數。如果您有多個Threads需要通知主線程怎麼辦?

您将看到的另一種解決方案是使用協程而不是Thread。這并不能正常工作。對套接字使用協程不會更改任何内容。您仍然會遇到當機問題。您必須堅持自己的Thread代碼或使用Async。

執行此操作的正确方法之一是建立一個集合,例如List。當您需要在主線程中執行某些操作時,調用一個函數,該函數存儲要在中執行的代碼Action。複制List的Action到本地List的Action,然後從本地執行的代碼Action在List随後清除List。這樣可以避免其他人Threads等待它完成執行。

您還需要添加一個,volatile boolean以通知Update函數中有等待List執行的代碼。将拷貝List到localList時,應将其包裝在lock關鍵字周圍,以防止另一個線程對其進行寫入。

UnityThread用法:

這種實作允許你調用的函數3個最常用的統一功能:Update,LateUpdate和FixedUpdate功能。這也允許您在主線程中調用運作協程函數。它可以擴充為能夠調用其他Unity回調函數中的函數,例如OnPreRender和OnPostRender。

1。首先,通過Awake()函數對其進行初始化。

void Awake()
{
    UnityThread.initUnityThread();
}
           

2。在子線程中執行主線程中Update函數的代碼,請執行以下操作:

UnityThread.executeInUpdate(() =>
{
    transform.Rotate(new Vector3(0f, 90f, 0f));
});
           

這會将scipt所連接配接的目前對象旋轉到90度。您現在可以transform.Rotate在另一個中使用Unity API()Thread。

3。在子線程中執行主線程中一個函數,請執行以下操作:

Action rot = Rotate;
UnityThread.executeInUpdate(rot);

void Rotate()
{
    transform.Rotate(new Vector3(0f, 90f, 0f));
}
           

在#2和#3樣品執行在Update功能。

4。在子線程中執行主線程中LateUpdate函數的代碼,請執行以下操作:

這樣的示例是錄影機跟蹤代碼。

UnityThread.executeInLateUpdate(()=>
{
    //Your code camera moving code
});
           

5。在子線程中執行主線程中FixedUpdate函數的代碼,請執行以下操作:

進行實體處理(例如施加力)時的這樣的例子Rigidbody。

UnityThread.executeInFixedUpdate(()=>
{
    //Your code physics code
});
           

6。在子線程中啟動主線程協程函數:

UnityThread.executeCoroutine(myCoroutine());

IEnumerator myCoroutine()
{
    Debug.Log("Hello");
    yield return new WaitForSeconds(2f);
    Debug.Log("Test");
}
           

最後,如果您不需要在LateUpdate和FixedUpdate函數中執行任何操作,則應在下面的代碼兩行中進行注釋:

//#define ENABLE_LATEUPDATE_FUNCTION_CALLBACK
//#define ENABLE_FIXEDUPDATE_FUNCTION_CALLBACK
           

這将提高性能。

UnityThread 腳本:

#define ENABLE_UPDATE_FUNCTION_CALLBACK
#define ENABLE_LATEUPDATE_FUNCTION_CALLBACK
#define ENABLE_FIXEDUPDATE_FUNCTION_CALLBACK

using System;
using System.Collections;
using UnityEngine;
using System.Collections.Generic;


public class UnityThread : MonoBehaviour
{
    //our (singleton) instance
    private static UnityThread instance = null;


    UPDATE IMPL
    //Holds actions received from another Thread. Will be coped to actionCopiedQueueUpdateFunc then executed from there
    private static List<System.Action> actionQueuesUpdateFunc = new List<Action>();

    //holds Actions copied from actionQueuesUpdateFunc to be executed
    List<System.Action> actionCopiedQueueUpdateFunc = new List<System.Action>();

    // Used to know if whe have new Action function to execute. This prevents the use of the lock keyword every frame
    private volatile static bool noActionQueueToExecuteUpdateFunc = true;


    LATEUPDATE IMPL
    //Holds actions received from another Thread. Will be coped to actionCopiedQueueLateUpdateFunc then executed from there
    private static List<System.Action> actionQueuesLateUpdateFunc = new List<Action>();

    //holds Actions copied from actionQueuesLateUpdateFunc to be executed
    List<System.Action> actionCopiedQueueLateUpdateFunc = new List<System.Action>();

    // Used to know if whe have new Action function to execute. This prevents the use of the lock keyword every frame
    private volatile static bool noActionQueueToExecuteLateUpdateFunc = true;



    FIXEDUPDATE IMPL
    //Holds actions received from another Thread. Will be coped to actionCopiedQueueFixedUpdateFunc then executed from there
    private static List<System.Action> actionQueuesFixedUpdateFunc = new List<Action>();

    //holds Actions copied from actionQueuesFixedUpdateFunc to be executed
    List<System.Action> actionCopiedQueueFixedUpdateFunc = new List<System.Action>();

    // Used to know if whe have new Action function to execute. This prevents the use of the lock keyword every frame
    private volatile static bool noActionQueueToExecuteFixedUpdateFunc = true;


    //Used to initialize UnityThread. Call once before any function here
    public static void initUnityThread(bool visible = false)
    {
        if (instance != null)
        {
            return;
        }

        if (Application.isPlaying)
        {
            // add an invisible game object to the scene
            GameObject obj = new GameObject("MainThreadExecuter");
            if (!visible)
            {
                obj.hideFlags = HideFlags.HideAndDontSave;
            }

            DontDestroyOnLoad(obj);
            instance = obj.AddComponent<UnityThread>();
        }
    }

    public void Awake()
    {
        DontDestroyOnLoad(gameObject);
    }

    //COROUTINE IMPL//
#if (ENABLE_UPDATE_FUNCTION_CALLBACK)
    public static void executeCoroutine(IEnumerator action)
    {
        if (instance != null)
        {
            executeInUpdate(() => instance.StartCoroutine(action));
        }
    }

    UPDATE IMPL
    public static void executeInUpdate(System.Action action)
    {
        if (action == null)
        {
            throw new ArgumentNullException("action");
        }

        lock (actionQueuesUpdateFunc)
        {
            actionQueuesUpdateFunc.Add(action);
            noActionQueueToExecuteUpdateFunc = false;
        }
    }

    public void Update()
    {
        if (noActionQueueToExecuteUpdateFunc)
        {
            return;
        }

        //Clear the old actions from the actionCopiedQueueUpdateFunc queue
        actionCopiedQueueUpdateFunc.Clear();
        lock (actionQueuesUpdateFunc)
        {
            //Copy actionQueuesUpdateFunc to the actionCopiedQueueUpdateFunc variable
            actionCopiedQueueUpdateFunc.AddRange(actionQueuesUpdateFunc);
            //Now clear the actionQueuesUpdateFunc since we've done copying it
            actionQueuesUpdateFunc.Clear();
            noActionQueueToExecuteUpdateFunc = true;
        }

        // Loop and execute the functions from the actionCopiedQueueUpdateFunc
        for (int i = 0; i < actionCopiedQueueUpdateFunc.Count; i++)
        {
            actionCopiedQueueUpdateFunc[i].Invoke();
        }
    }
#endif

    LATEUPDATE IMPL
#if (ENABLE_LATEUPDATE_FUNCTION_CALLBACK)
    public static void executeInLateUpdate(System.Action action)
    {
        if (action == null)
        {
            throw new ArgumentNullException("action");
        }

        lock (actionQueuesLateUpdateFunc)
        {
            actionQueuesLateUpdateFunc.Add(action);
            noActionQueueToExecuteLateUpdateFunc = false;
        }
    }


    public void LateUpdate()
    {
        if (noActionQueueToExecuteLateUpdateFunc)
        {
            return;
        }

        //Clear the old actions from the actionCopiedQueueLateUpdateFunc queue
        actionCopiedQueueLateUpdateFunc.Clear();
        lock (actionQueuesLateUpdateFunc)
        {
            //Copy actionQueuesLateUpdateFunc to the actionCopiedQueueLateUpdateFunc variable
            actionCopiedQueueLateUpdateFunc.AddRange(actionQueuesLateUpdateFunc);
            //Now clear the actionQueuesLateUpdateFunc since we've done copying it
            actionQueuesLateUpdateFunc.Clear();
            noActionQueueToExecuteLateUpdateFunc = true;
        }

        // Loop and execute the functions from the actionCopiedQueueLateUpdateFunc
        for (int i = 0; i < actionCopiedQueueLateUpdateFunc.Count; i++)
        {
            actionCopiedQueueLateUpdateFunc[i].Invoke();
        }
    }
#endif

    FIXEDUPDATE IMPL//
#if (ENABLE_FIXEDUPDATE_FUNCTION_CALLBACK)
    public static void executeInFixedUpdate(System.Action action)
    {
        if (action == null)
        {
            throw new ArgumentNullException("action");
        }

        lock (actionQueuesFixedUpdateFunc)
        {
            actionQueuesFixedUpdateFunc.Add(action);
            noActionQueueToExecuteFixedUpdateFunc = false;
        }
    }

    public void FixedUpdate()
    {
        if (noActionQueueToExecuteFixedUpdateFunc)
        {
            return;
        }

        //Clear the old actions from the actionCopiedQueueFixedUpdateFunc queue
        actionCopiedQueueFixedUpdateFunc.Clear();
        lock (actionQueuesFixedUpdateFunc)
        {
            //Copy actionQueuesFixedUpdateFunc to the actionCopiedQueueFixedUpdateFunc variable
            actionCopiedQueueFixedUpdateFunc.AddRange(actionQueuesFixedUpdateFunc);
            //Now clear the actionQueuesFixedUpdateFunc since we've done copying it
            actionQueuesFixedUpdateFunc.Clear();
            noActionQueueToExecuteFixedUpdateFunc = true;
        }

        // Loop and execute the functions from the actionCopiedQueueFixedUpdateFunc
        for (int i = 0; i < actionCopiedQueueFixedUpdateFunc.Count; i++)
        {
            actionCopiedQueueFixedUpdateFunc[i].Invoke();
        }
    }
#endif

    public void OnDisable()
    {
        if (instance == this)
        {
            instance = null;
        }
    }
}
           

附上原文連結