从另一个线程使用Unity API或在主线程中调用函数

51

我的问题是我尝试使用Unity套接字来实现某些功能。每当我收到新消息时,我需要将其更新到updattext(它是一个Unity Text)。然而,当我执行以下代码时,void update并没有每次调用。

我没有在void getInformation中包括updatetext.GetComponent<Text>().text = "From server: "+tempMesg;的原因是这个函数在线程中,当我在getInformation()中包括它时,就会出现错误:

getcomponentfastpath只能从主线程调用

我认为问题是我不知道如何同时在C#中运行主线程和子线程?或者可能还有其他问题。

这是我的代码:

using UnityEngine;
using System.Collections;
using System;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using UnityEngine.UI;


public class Client : MonoBehaviour {

    System.Net.Sockets.TcpClient clientSocket = new System.Net.Sockets.TcpClient();
    private Thread oThread;

//  for UI update
    public GameObject updatetext;
    String tempMesg = "Waiting...";

    // Use this for initialization
    void Start () {
        updatetext.GetComponent<Text>().text = "Waiting...";
        clientSocket.Connect("10.132.198.29", 8888);
        oThread = new Thread (new ThreadStart (getInformation));
        oThread.Start ();
        Debug.Log ("Running the client");
    }

    // Update is called once per frame
    void Update () {
        updatetext.GetComponent<Text>().text = "From server: "+tempMesg;
        Debug.Log (tempMesg);
    }

    void getInformation(){
        while (true) {
            try {
                NetworkStream networkStream = clientSocket.GetStream ();
                byte[] bytesFrom = new byte[10025];
                networkStream.Read (bytesFrom, 0, (int)bytesFrom.Length);
                string dataFromClient = System.Text.Encoding.ASCII.GetString (bytesFrom);
                dataFromClient = dataFromClient.Substring (0, dataFromClient.IndexOf ("$"));
                Debug.Log (" >> Data from Server - " + dataFromClient);

                tempMesg = dataFromClient;

                string serverResponse = "Last Message from Server" + dataFromClient;

                Byte[] sendBytes = Encoding.ASCII.GetBytes (serverResponse);
                networkStream.Write (sendBytes, 0, sendBytes.Length);
                networkStream.Flush ();
                Debug.Log (" >> " + serverResponse);

            } catch (Exception ex) {
                Debug.Log ("Exception error:" + ex.ToString ());
                oThread.Abort ();
                oThread.Join ();
            }
//          Thread.Sleep (500);
        }
    }
}

这里是我的答案,请看一下:https://stackoverflow.com/questions/54206912/c-unity-failing-to-call-unity-method-from-asynchronous-method/54213244#54213244 - auslander
5个回答

84

Unity不是线程安全的,因此他们决定通过添加机制,在从另一个线程调用API时抛出异常,使其不可能从另一个线程调用其API。

这个问题已经被问了很多次,但没有一个合适的解决方案/答案。答案通常是“使用插件”或做一些不安全的线程操作。希望这将是最后一个问题。

你通常会在Stackoverflow或Unity的论坛网站上看到的解决方法是简单地使用一个布尔变量让主线程知道你需要在主线程中执行代码。这是不正确的,因为它不是线程安全的,并且不允许你控制要调用哪个函数。如果你有多个线程需要通知主线程怎么办?

另一个你会看到的解决方案是使用协程而不是 Thread。这种方法无法奏效。对于套接字使用协程不会改变任何事情。你仍然会遇到冻结问题。你必须坚持使用你的Thread代码或使用Async
其中一种正确的方法是创建一个集合,例如List。当你需要在主线程中执行某些操作时,请调用一个函数,该函数将要执行的代码存储在一个Action中。将那个ActionList复制到一个本地的ActionList中,然后从该List中执行本地的Action中的代码,最后清除该List。这可以防止其他Threads等待它完成执行。
您还需要添加一个volatile boolean来通知Update函数,有代码在List中等待执行。在将List复制到本地List时,应该将其包装在lock关键字周围,以防止另一个线程对其进行写入。
执行上述操作的脚本: 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;
        }
    }
}

用法:

此实现允许您在Unity中调用3个最常用的函数:UpdateLateUpdateFixedUpdate函数。这还允许您在主线程中运行协程函数。它可以扩展为能够在其他Unity回调函数(如OnPreRenderOnPostRender)中调用函数。

1.首先,从Awake()函数中进行初始化。

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

2. 从另一个线程执行主线程中的代码:

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

这将使当前对象旋转90度,脚本已附加。您现在可以在另一个线程中使用Unity API(transform.Rotate)。
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.从另一个线程开始在主Thread中运行协程函数:

UnityThread.executeCoroutine(myCoroutine());

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

最后,如果您不需要在LateUpdateFixedUpdate函数中执行任何操作,则应该注释掉下面这段代码的两行:
//#define ENABLE_LATEUPDATE_FUNCTION_CALLBACK
//#define ENABLE_FIXEDUPDATE_FUNCTION_CALLBACK

这将提高性能。


1
抱歉...我尝试实现您的解决方案...但是当我键入UnityThread.initUnityThread();时,它显示错误,即“UnityThread”在当前上下文中不存在...对于刚接触Unity的人来说很抱歉...您能否更详细地解释一下您的代码?...非常感谢.. - user6142261
1
executeCoroutine 需要放在 #if (ENABLE_UPDATE_FUNCTION_CALLBACK) 内,否则当符号未定义时,在 executeInUpdate(() => instance.StartCoroutine(action)); 行会出现编译器错误。 - Scott Chamberlain
3
如果ENABLE_UPDATE_FUNCTION_CALLBACK未被定义,该函数将不会被包括在内。这是问题所在,你所说的情况并没有发生。函数public static void executeCoroutine(IEnumerator action)位于#if代码块之前,因此如果未定义ENABLE_UPDATE_FUNCTION_CALLBACK,函数executeCoroutine仍将存在。我想表达的是需要将#if语句提前12行,放在COROUTINE IMPL注释之前,以便当符号未被定义时,两个函数executeCoroutineexecuteInUpdate都不存在。 - Scott Chamberlain
感谢@程序员分享这个很棒的解决方案!我会尝试获取外部IP(WAN)。 - Ismoh
1
为什么不使用SynchronizationContext.Current.Post((object state) => { YourFunction(); }, this); - Saleh
显示剩余12条评论

15

我一直在使用这个解决方案来解决这个问题。创建一个带有以下代码的脚本并将其附加到游戏对象:

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

public class ExecuteOnMainThread : MonoBehaviour {

    public static readonly ConcurrentQueue<Action> RunOnMainThread = new ConcurrentQueue<Action>();
        
    void Update()
    {
        if(!RunOnMainThread.IsEmpty)
        {
           while(RunOnMainThread.TryDequeue(out var action))
           {
             action?.Invoke();
           }
        }
    }
}

那么当你需要在主线程上调用某些东西并从应用程序中的任何其他函数访问Unity API时:

ExecuteOnMainThread.RunOnMainThread.Enqueue(() => {

    // Code here will be called in the main thread...

});

根据 @dynamoid 的建议,我已经更新了代码。我发现队列本身是不安全的,需要加锁。我对这种方法仍然不确定。我的主要用途是从同时运行的 C++ 代码中收集数据。 - Pelayo Méndez
“RunOnMainThread”不需要以这种方式访问吗?;) @EgemenErtuğrul为什么删除了它? - derHugo
由于静态类“ExecuteOnMainThread”不能从类型“UnityEngine.MonoBehaviour”派生。 静态类必须从对象派生。 - jdnichollsc
@jdnichollsc,我不是在谈论类类型ExecuteOnMainThread,因为我们可以看到它不是静态的,并且派生自MonoBehaviour,而是关于成员RunOnMainThread,如果它是static,则只能通过类型本身访问,这是所示用例中预期的方式...一个编辑已经删除了它。 - derHugo

11

有关Unity线程的许多写作都是不正确的。

为什么这样说呢?

当然,Unity完全基于帧。

在帧为基础的系统中工作时,线程问题会完全不同。

在基于帧的系统中,线程问题完全不同。(事实上,通常更容易处理。)

假设您有一个Unity温度计显示屏,显示某个值

Thermo.cs

在此输入图片描述

因此,它将具有一个在Update中调用的函数,例如:

func void ShowThermoValue(float fraction) {
   display code to show the current thermometer value
}

请注意,Unity中的“Update”函数仅意味着“每帧运行一次”。

它每帧只运行一次,就是这样。

(自然地,它仅在“主线程”上运行。在Unity中没有其他线程!只有…“Unity线程”!)

在其他地方,也许是在“IncomingData.cs”中,你需要编写一个函数来处理“一个新值已经到达”的概念:

[MonoPInvokeCallback(typeof(ipDel))]
public static void NewValueArrives(float f) {

    ... ???
}

请注意,当然,那是一个类函数!还能是什么呢?

您无法“进入”普通的Unity函数(例如ShowThermoValue)。 那毫无意义-它只是每帧运行一次的函数。脚注1

假设:值经常且不规则地到达。

想象一下,您连接了一些科学设备(例如红外热计)到一排PC上

这些电子设备非常频繁地提供新的“温度”值。 假设每帧有数十次。

因此,“NewValueArrives”每秒被调用100次。

那么你要怎么做呢?

它不能更简单了。

从到达值线程中,你所要做的就是...................等待它.............在组件中设置一个变量!!

WTF? 你只需要设置一个变量吗? 这就是全部吗? 它怎么能这么简单?

这是其中一种不寻常的情况:

  1. Unity线程编写的大部分内容都是完全无望的。

  2. 令人惊讶的是,实际方法非常简单

  3. 它如此简单,以至于您可能认为自己在做错事情!!

所以有了变量......

[System.Nonserialized] public float latestValue;

从“到达线程”中设置它...

[MonoPInvokeCallback(typeof(ipDel))]
public static void NewValueArrives(float f) {

    ThisScript.runningInstance.latestValue = f; // done
}

就这样了。

基本上,要成为“Unity中最伟大的线程专家”——显然是基于帧的——没有比以上更多的事情需要做了。

每当ShowThermoValue在每一帧被调用时,只需简单地显示该值!

真的,就这样!

[System.Nonserialized] public float latestValue;
func void ShowThermoValue() { // note NO arguments here!
   display code, draws a thermometer
   thermo height = latestValue
}

您只是展示了"最新"值。

latestValue 可能在该帧中已被设置过一次、两次、十次或一百次.......... 但是,当ShowThermoValue运行该帧时,您只会显示当前的值!

还有什么可以显示的呢?

温度计正在以60fps更新屏幕,因此您显示的是最新值。 脚注2

事实上,就是这么简单。 真正的如此简单。 令人惊讶但却是真实的。


#(重要说明-不要忘记,在Unity/C#中,vector3等都不是原子的)

正如用户@dymanoid指出的那样(请阅读下面的重要讨论),重要的是要记住,虽然float在Unity/C#环境中是原子的,但其他任何东西(比如Vector3等)都不是原子的。通常情况下(就像这个例子一样),您只从计算中传递floats,例如来自本机插件、温度计等。但是必须注意向量等都不是原子的。


有时,有经验的线程程序员会在基于帧的系统中遇到一些麻烦,因为:在基于帧的系统中,由竞赛和锁定问题引起的大多数问题......在概念上不存在。

在基于帧的系统中,任何游戏项目都应该简单地显示或根据某些"当前值"进行行为,而这些值是在某个地方设置的。如果有来自其他线程的信息,只需设置这些值-就完成了

在Unity中,您不能有意义地"与主线程对话",因为该主线程.............是基于帧的!

大多数锁定、阻塞和竞赛轨迹问题在基于帧的范例中不存在,因为:如果您在特定帧中设置latestValue十次、一百万次、十亿次,那么您能做什么?......在该帧期间,您只能显示一个值!

想象一下老式的塑料薄膜。你真的只有一个......画面,就是这样。如果您在特定帧中将latestValue设置为一万亿次,则ShowThermoValue仅在运行时显示(60分之一秒)抓取的一个值。

您需要做的就是:将信息留在某个地方,如果该帧想要使用,则基于帧的系统会利用它。

这就是简介。

因此,大多数"线程问题"在Unity中都消失了

你只能从其他计算线程或插件线程中

  • 下降值,游戏可能会使用它们。

就是这样!

让我们考虑问题的标题...

如何在主线程中“调用函数”

这是完全没有意义的。Unity中的“函数”只是框架引擎每帧运行一次的函数。

你不能在Unity中“调用”任何东西。框架引擎每帧会运行很多东西(很多东西)。

请注意,线程实际上与此毫不相关。如果Unity使用十亿个线程或量子计算进行运行,那都不会影响任何事情。

在基于帧的系统中,你无法“调用函数”。

幸运的是,采取的方法非常简单,你只需要设置值,然后基于帧的函数可以在需要时查看它们!就是这么简单。


脚注


1你怎么可能?作为一个思想实验,忘记你正在不同的线程上的问题。ShowThermoValue由框架引擎在每个帧上运行一次。你无法以任何有意义的方式“调用”它。与正常的OO软件不同,你不能创建类(一个组件??没有意义)的实例并运行该函数-那完全没有意义。

在“正常”的线程编程中,线程可以相互交流等等,在这样做时你需要关注锁定、竞态条件等等。但在ECS、基于帧的系统中,这一切都是没有意义的。没有什么可以“交流”的。

假设Unity实际上是多线程的!所以Unity的开发人员让所有引擎以多线程方式运行。 这不会有任何差别-你无法以任何有意义的方式进入ShowThermoValue!它是一个组件,每帧由框架引擎运行一次,就这样。

因此NewValueArrives位于任何地方-它是一个类函数!

让我们回答标题中的问题:

“从另一个线程使用Unity API或在主线程中调用函数?”

这个概念是完全没有意义的。Unity(像所有游戏引擎一样)是基于帧的。没有“调用”主线程上的函数的概念。打个比方:这就像电影摄影师在胶片电影时代问如何在一个帧上实际地“移动”东西。

enter image description here

当然这毫无意义。你所能做的只是改变下一张照片、下一帧的内容。


2 我指的是"到达值线程"......事实上!NewValueArrives 可以在主线程上运行,也可以不在主线程上运行!!它可以在插件线程上运行,也可以在其他线程上运行!在处理 NewValueArrives 调用时,它实际上可能完全是单线程的!这并不重要! 在基于帧的模式中,你所能做的就是“留下”信息,供 ShowThermoValue 组件等根据需要使用。


3
评论不适合进行长时间的讨论;此对话已被移至聊天室,请移步参与。 - Bhargav Rao
一位管理员不幸地删除了一个关键的技术讨论。用户@dymanoid指出,在Unity/C#环境中,浮点数是原子性的,但需要注意的是,像Vector3这样的东西在Unity/C#环境中不是原子性的 - Fattie
10
@Fattie,我也指出了多线程问题不仅涉及竞态条件(原子操作可能很重要),还有其他一些多线程陷阱,比如指令重新排序或内存屏障,而这些都很容易在Unity中发生。因此,Unity必须应对所有众所周知的多线程问题。你的说法“Unity上的线程问题是不同的,某些概念根本不存在”是不正确和误导性的,因此你的答案也是错误的。Unity基于.NET运行时(Mono),所有.NET运行时的规则都适用。 - dymanoid
@dymanoid,谢谢,我不想进行长时间的讨论,但正如我所说,“某些概念根本不存在”,并且正如我所说,“Unity上的线程问题是不同的”。 (您指出了一些确实存在的问题,当然是真的,但这不是此QA的重点。)归根结底,请考虑我的朋友程序员在上面的答案-它实际上是错误的 - 在Unity中,您根本不会做任何类似的事情;您所做的只是像我的温度计示例(“NewValueArrives”等)一样简单而明确。 - Fattie
1
如果你向列表中“丢入一个值”,你经常会抛出ConcurrentModificationException异常。 - DoctorPangloss
这个解决方案仅适用于非常少量的问题,特别是那些唯一的任务是从不同的线程获取信息到主线程的问题。然而,许多情况也需要相反的交互,即从主线程获取信息到另一个线程。这也是这个问题的情况,因为他们想要调用 getComponent<>()。在这种特殊情况下,您可能能够解决此问题,但通常这是更大的问题。 - RcCookie

10

另一种在主线程上运行代码的解决方案,但不需要游戏对象和MonoBehavior,是使用SynchronizationContext

// On main thread, during initialization:
var syncContext = System.Threading.SynchronizationContext.Current;

// On your worker thread
syncContext.Post(_ =>
{
    // This code here will run on the main thread
    Debug.Log("Hello from main thread!");
}, null);

1
这在Unity IOS平台上运行良好,但在Unity Android平台上线程为空。 - sanmeet

0

一起使用UniRx的多线程模式,UniTask和RxSocket。

使用UniRx的多线程模式, UniTaskRxSocket

[SerializeField] private Text m_Text;

async UniTaskVoid Connect() {
    IPEndPoint endPoint = new IPEndPoint(IPAddress.IPv6Loopback, 12345);

    // Create a socket client by connecting to the server at the IPEndPoint.
    // See the UniRx Async tooling to use await 
    IRxSocketClient client = await endPoint.ConnectRxSocketClientAsync();

    client.ReceiveObservable
        .ToStrings()
        .ObserveOnMainThread()
        .Subscribe(onNext: message =>
    {
        m_Text.text = message;
    }).AddTo(this);

    // Send a message to the server.
    client.Send("Hello!".ToByteArray());
}

1
非常抱歉,但基本概念在基于帧的系统上根本行不通。UniRx和UniTask是完全误导的。例如,在UniRx上,我们看到句子“IEnumerator(Coroutine)是Unity的原始异步工具”,这完全是错误的。在Unity中,协同程序与线程或异步性没有任何关系,就像海洋与高尔夫球一样:)我上面提供的极其简单的“温度计”示例完全解释了如何在Unity中实际执行“此类操作”。 - Fattie
1
你正在阅读这条评论并学习Unity吗?尝试学习这里展示的包和模式,它们展示了专业技能。这个解决方案是最正确和简洁的,并且正确地实现了其他评论中的想法。你将采用像响应式编程这样的实践,可以在许多应用程序和语言中使用。 - DoctorPangloss
1
同意@DoctorPangloss的观点。如果你不习惯使用Rx,这也可能会感觉不自然,但对我来说完全有道理。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接