前言

        Unity的大部分API(例如,与游戏对象交互,修改组件属性等)都需要在主线程中调用。然而,有时你可能在另一个线程(例如,网络请求,长时间运行的计算等)中完成一些工作,并且在完成后需要更新Unity的某些东西。在这种情况下,你不能直接从那个线程调用Unity API,因为这可能会导致未定义的行为或错误。

        虽然你可以在其他线程中进行计算密集型的任务(例如,AI计算,物理模拟等),但是你不能在其他线程中直接调用Unity的API。因此你需要类似DwkUnityMainThreadDispatcher这样的类,它可以让你在其他线程中安全地调用Unity的API。

        例如,你可以这样使用它:

 

DwkUnityMainThreadDispatcher.Instance().Enqueue(() =>
{
    // 在这里调用Unity API
});

 

UnityMainThreadDispatcher类代码

        DwkUnityMainThreadDispatcher类原貌:

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

public class DwkUnityMainThreadDispatcher : MonoBehaviour
{
    private static DwkUnityMainThreadDispatcher instance;
    private readonly Queue<System.Action> actions = new Queue<System.Action>();

    private void Awake()
    {
        if (instance == null)
        {
            instance = this;
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(gameObject);
        }
    }

    public static DwkUnityMainThreadDispatcher Instance()
    {
        if (!instance)
        {
            throw new System.Exception("UnityMainThreadDispatcher could not find the UnityMainThreadDispatcher object. Please ensure you have added the MainThreadExecutor Prefab to your scene.");
        }
        return instance;
    }

    public void Enqueue(System.Action action)
    {
        lock (actions)
        {
            actions.Enqueue(action);
        }
    }

    public void Update()
    {
        while (actions.Count > 0)
        {
            actions.Dequeue().Invoke();
        }
    }
}

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

        if (sum > 0)
        {
            DwkUnityMainThreadDispatcher.Instance().Enqueue(() =>
            {
                audioSource1.PlayOneShot(audioSource1.clip);
            });
            //Debug.Log("加分特效");
        }

附录:

实现单例模式的标准流程

private static DwkUnityMainThreadDispatcher instance;

private void Awake()
{
    if (instance == null)
    {
        instance = this;
        DontDestroyOnLoad(gameObject);
    }
    else
    {
        Destroy(gameObject);
    }
}

public static DwkUnityMainThreadDispatcher Instance()
{
    if (!instance)
    {
        throw new System.Exception("UnityMainThreadDispatcher could not find the UnityMainThreadDispatcher object. Please ensure you have added the MainThreadExecutor Prefab to your scene.");
    }
    return instance;
}

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

  1. 声明一个私有静态变量instance来存储单例实例。

  2. Awake()方法中,检查instance是否为null。如果为null,则将instance设置为当前实例,并使用DontDestroyOnLoad(gameObject)确保在加载新场景时不会销毁这个对象。如果instance不为null,则销毁当前游戏对象,这样可以确保只有一个DwkUnityMainThreadDispatcher实例存在。

  3. 提供一个公共静态方法Instance()来获取单例实例。如果instancenull,则抛出一个异常,提示用户需要在场景中添加MainThreadExecutor Prefab。

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

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

private readonly Queue<System.Action> actions = new Queue<System.Action>();

public void Enqueue(System.Action action)
{
    lock (actions)
    {
        actions.Enqueue(action);
    }
}

public void Update()
{
    while (actions.Count > 0)
    {
        actions.Dequeue().Invoke();
    }
}


 

  1. 在其他线程中,当你需要执行一些必须在主线程中进行的操作(例如,调用Unity的API)时,你可以创建一个System.Action,这个System.Action包含了你想要执行的操作。

  2. 你将这个System.Action传递给Enqueue方法。Enqueue方法将这个System.Action添加到actions队列中。这个过程是线程安全的,因为Enqueue方法使用了lock关键字来确保在多线程环境下,添加任务到队列的操作是线程安全的。

  3. 在主线程中,Unity每一帧都会调用Update方法。在Update方法中,它会检查actions队列中是否有任务。如果有,它会使用Dequeue方法取出队列中的第一个任务,并使用Invoke方法执行这个任务。这个过程会一直进行,直到actions队列中没有任务为止。

        这样,你就可以在其他线程中安全地调度任务到主线程中执行了。这对于在其他线程中进行一些耗时的操作,然后需要更新Unity的某些东西(例如,更新UI,创建游戏对象等)非常有用。

  1. private readonly Queue<System.Action> actions = new Queue<System.Action>(); 这行代码创建了一个队列,用于存储要在主线程中执行的任务。每个任务都是一个System.Action,也就是一个无参数且无返回值的委托。

  2. public void Enqueue(System.Action action) 这个方法用于将一个任务添加到队列中。这个任务将在主线程中执行。lock (actions)这行代码确保了在多线程环境下,添加任务到队列的操作是线程安全的。

  3. public void Update() 这个方法在每一帧都会被Unity调用,它在主线程中执行。在这个方法中,它会执行队列中的所有任务。这是通过在队列中有任务时调用actions.Dequeue().Invoke()来实现的。Dequeue()方法移除并返回队列中的第一个任务,Invoke()方法则执行这个任务。

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

04-06 01:25