天天看点

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;
        }
    }
}
           

附上原文链接