UnityMainThreadDispatcher 有什么作用?

tai*_*chi 6 c# multithreading unity-game-engine

我读了这段代码。

https://github.com/johnjcsmith/iPhoneMoCapUnity/blob/master/Assets/NetworkMeshAnimator.cs

代码的第 62 行周围有以下说明。

这是在做什么处理?

    if (UnityMainThreadDispatcher.Exists()) {
        dispatcher = UnityMainThreadDispatcher.Instance ();
    } 
Run Code Online (Sandbox Code Playgroud)

GitHub 上有代码,但它是 Unity 中的标准功能吗?

Ayb*_*ybe 7

这就是为什么您需要在 Unity 中使用伪调度程序的原因:

在 Unity 中,大多数对象只能从 Unity 主线程中创建。

但是假设您有一些繁重的任务想要在后台运行,例如 with Task.Run,在您的任务中,您将无法像上述那样实例化此类对象,但仍然希望如此。

解决这个问题有很多解决方案,但它们都利用了相同的东西:

捕获 Unity 的同步上下文并向其发布消息

这是这样做的原始方法,灵感来自 Raymond Chen 的 The Old New Thing:

C++/WinRT 羡慕:将线程切换任务引入 C#(WPF 和 WinForms 版)

概念如下:在方法中随时切换到特定线程!

公共类型

线程切换器:

using System.Runtime.CompilerServices;
using JetBrains.Annotations;

namespace Threading
{
    /// <summary>
    ///     Defines an object that switches to a thread.
    /// </summary>
    [PublicAPI]
    public interface IThreadSwitcher : INotifyCompletion
    {
        bool IsCompleted { get; }

        IThreadSwitcher GetAwaiter();

        void GetResult();
    }
}
Run Code Online (Sandbox Code Playgroud)

线程切换器:

using Threading.Internal;

namespace Threading
{
    /// <summary>
    ///     Switches to a particular thread.
    /// </summary>
    public static class ThreadSwitcher
    {
        /// <summary>
        ///     Switches to the Task thread.
        /// </summary>
        /// <returns></returns>
        public static IThreadSwitcher ResumeTaskAsync()
        {
            return new ThreadSwitcherTask();
        }

        /// <summary>
        ///     Switch to the Unity thread.
        /// </summary>
        /// <returns></returns>
        public static IThreadSwitcher ResumeUnityAsync()
        {
            return new ThreadSwitcherUnity();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

私有类型

线程切换任务:

using System;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;

namespace Threading.Internal
{
    internal struct ThreadSwitcherTask : IThreadSwitcher
    {
        public IThreadSwitcher GetAwaiter()
        {
            return this;
        }

        public bool IsCompleted => SynchronizationContext.Current == null;

        public void GetResult()
        {
        }

        public void OnCompleted([NotNull] Action continuation)
        {
            if (continuation == null)
                throw new ArgumentNullException(nameof(continuation));

            Task.Run(continuation);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

ThreadSwitcherUnity:

using System;
using System.Threading;
using JetBrains.Annotations;

namespace Threading.Internal
{
    internal struct ThreadSwitcherUnity : IThreadSwitcher
    {
        public IThreadSwitcher GetAwaiter()
        {
            return this;
        }

        public bool IsCompleted => SynchronizationContext.Current == UnityThread.Context;

        public void GetResult()
        {
        }

        public void OnCompleted([NotNull] Action continuation)
        {
            if (continuation == null)
                throw new ArgumentNullException(nameof(continuation));

            UnityThread.Context.Post(s => continuation(), null);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

统一线程:

using System.Threading;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;

#endif

namespace Threading.Internal
{
    internal static class UnityThread
    {
#pragma warning disable IDE0032 // Use auto property
        private static SynchronizationContext _context;
#pragma warning restore IDE0032 // Use auto property

        public static SynchronizationContext Context => _context;

#if UNITY_EDITOR
        [InitializeOnLoadMethod]
#endif
        [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
        private static void Capture()
        {
            _context = SynchronizationContext.Current;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

例子

尽管它是一种奇特的方法,但它具有巨大的优势,即通过使用单个调用,您可以在同一方法的不同线程中进行工作。以下代码Task.Run在实例化 Unity 对象时使用但不会产生任何错误,因为它是在正确的线程中完成的。

private static async Task DoWork(CancellationToken token)
{
    token.ThrowIfCancellationRequested();

    var gameObjects = new List<GameObject>();

    await ThreadSwitcher.ResumeUnityAsync();

    for (var i = 0; i < 25; i++)
    {
        if (token.IsCancellationRequested)
            token.ThrowIfCancellationRequested();

        await Task.Delay(125, token);

        var gameObject = new GameObject(i.ToString());

        gameObjects.Add(gameObject);
    }
}
Run Code Online (Sandbox Code Playgroud)

现在由您来细分您的工作,因为本质上 Unity 同步上下文并不意味着运行繁重的计算,而只是实例化您无法从另一个线程执行的内容。

一个简单的例子是生成一些程序网格:

  • 完成任务中的所有数学运算并生成足够的数据来创建网格
    • 即顶点、法线、颜色、紫外线
  • 切换到 Unity 线程
    • 从这个数据创建一个网格,PERIOD,这将足够快,以至于无法察觉

这是一个有趣的问题,我希望我已经回答了!