如何在其他线程中安全地调用Unity的API?

打印 上一主题 下一主题

主题 909|帖子 909|积分 2727

前言


        Unity的大部分API(例如,与游戏对象交互,修改组件属性等)都需要在主线程中调用。然而,偶然你可能在另一个线程(例如,网络请求,长时间运行的盘算等)中完成一些工作,并且在完成后需要更新Unity的某些东西。在这种情况下,你不能直接从那个线程调用Unity API,因为这可能会导致未定义的举动或错误。
        虽然你可以在其他线程中举行盘算麋集型的使命(例如,AI盘算,物理模拟等),但是你不能在其他线程中直接调用Unity的API。因此你需要类似DwkUnityMainThreadDispatcher如许的类,它可以让你在其他线程中安全地调用Unity的API。
        例如,你可以如许使用它:
 
  1. DwkUnityMainThreadDispatcher.Instance().Enqueue(() =>
  2. {
  3.     // 在这里调用Unity API
  4. });
复制代码
 
UnityMainThreadDispatcher类代码


        DwkUnityMainThreadDispatcher类原貌:

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. public class DwkUnityMainThreadDispatcher : MonoBehaviour
  5. {
  6.     private static DwkUnityMainThreadDispatcher instance;
  7.     private readonly Queue<System.Action> actions = new Queue<System.Action>();
  8.     private void Awake()
  9.     {
  10.         if (instance == null)
  11.         {
  12.             instance = this;
  13.             DontDestroyOnLoad(gameObject);
  14.         }
  15.         else
  16.         {
  17.             Destroy(gameObject);
  18.         }
  19.     }
  20.     public static DwkUnityMainThreadDispatcher Instance()
  21.     {
  22.         if (!instance)
  23.         {
  24.             throw new System.Exception("UnityMainThreadDispatcher could not find the UnityMainThreadDispatcher object. Please ensure you have added the MainThreadExecutor Prefab to your scene.");
  25.         }
  26.         return instance;
  27.     }
  28.     public void Enqueue(System.Action action)
  29.     {
  30.         lock (actions)
  31.         {
  32.             actions.Enqueue(action);
  33.         }
  34.     }
  35.     public void Update()
  36.     {
  37.         while (actions.Count > 0)
  38.         {
  39.             actions.Dequeue().Invoke();
  40.         }
  41.     }
  42. }
复制代码

举例:在非主线程中播放Unity声音


  1.         if (sum > 0)
  2.         {
  3.             DwkUnityMainThreadDispatcher.Instance().Enqueue(() =>
  4.             {
  5.                 audioSource1.PlayOneShot(audioSource1.clip);
  6.             });
  7.             //Debug.Log("加分特效");
  8.         }
复制代码

附录:


实现单例模式的标准流程


  1. private static DwkUnityMainThreadDispatcher instance;
  2. private void Awake()
  3. {
  4.     if (instance == null)
  5.     {
  6.         instance = this;
  7.         DontDestroyOnLoad(gameObject);
  8.     }
  9.     else
  10.     {
  11.         Destroy(gameObject);
  12.     }
  13. }
  14. public static DwkUnityMainThreadDispatcher Instance()
  15. {
  16.     if (!instance)
  17.     {
  18.         throw new System.Exception("UnityMainThreadDispatcher could not find the UnityMainThreadDispatcher object. Please ensure you have added the MainThreadExecutor Prefab to your scene.");
  19.     }
  20.     return instance;
  21. }
复制代码

        这个单例模式的实现步骤如下:


  • 声明一个私有静态变量instance来存储单例实例。
  • 在Awake()方法中,检查instance是否为null。如果为null,则将instance设置为当前实例,并使用DontDestroyOnLoad(gameObject)确保在加载新场景时不会销毁这个对象。如果instance不为null,则销毁当前游戏对象,如许可以确保只有一个DwkUnityMainThreadDispatcher实例存在。
  • 提供一个公共静态方法Instance()来获取单例实例。如果instance为null,则抛出一个异常,提示用户需要在场景中添加MainThreadExecutor Prefab。

        这种方式的单例模式在Unity中很常见,因为它可以确保在整个游戏运行期间,只有一个DwkUnityMainThreadDispatcher实例存在,而且可以在任何地方通过DwkUnityMainThreadDispatcher.Instance()访问这个实例。

允许其他线程将使命安全地调度到主线程中执行


  1. private readonly Queue<System.Action> actions = new Queue<System.Action>();
  2. public void Enqueue(System.Action action)
  3. {
  4.     lock (actions)
  5.     {
  6.         actions.Enqueue(action);
  7.     }
  8. }
  9. public void Update()
  10. {
  11.     while (actions.Count > 0)
  12.     {
  13.         actions.Dequeue().Invoke();
  14.     }
  15. }
复制代码

 

  • 在其他线程中,当你需要执行一些必须在主线程中举行的操作(例如,调用Unity的API)时,你可以创建一个System.Action,这个System.Action包含了你想要执行的操作。
  • 你将这个System.Action通报给Enqueue方法。Enqueue方法将这个System.Action添加到actions队列中。这个过程是线程安全的,因为Enqueue方法使用了lock关键字来确保在多线程环境下,添加使命到队列的操作是线程安全的。
  • 在主线程中,Unity每一帧都会调用Update方法。在Update方法中,它会检查actions队列中是否有使命。如果有,它会使用Dequeue方法取出队列中的第一个使命,并使用Invoke方法执行这个使命。这个过程会一直举行,直到actions队列中没有使命为止。
        如许,你就可以在其他线程中安全地调度使命到主线程中执行了。这对于在其他线程中举行一些耗时的操作,然后需要更新Unity的某些东西(例如,更新UI,创建游戏对象等)非常有效。


  • private readonly Queue<System.Action> actions = new Queue<System.Action>(); 这行代码创建了一个队列,用于存储要在主线程中执行的使命。每个使命都是一个System.Action,也就是一个无参数且无返回值的委托。
  • public void Enqueue(System.Action action) 这个方法用于将一个使命添加到队列中。这个使命将在主线程中执行。lock (actions)这行代码确保了在多线程环境下,添加使命到队列的操作是线程安全的。
  • public void Update() 这个方法在每一帧都会被Unity调用,它在主线程中执行。在这个方法中,它会执行队列中的全部使命。这是通过在队列中有使命时调用actions.Dequeue().Invoke()来实现的。Dequeue()方法移除并返回队列中的第一个使命,Invoke()方法则执行这个使命。

        这部分代码提供了一种在主线程中安全执行使命的方式。你可以在任何线程中使用Enqueue方法来添加一个使命,这个使命将在主线程中执行。如许,你就可以安全地从任何线程调用Unity API了。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

莫张周刘王

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表