Что делает UnityMainThreadDispatcher? - PullRequest
       2

Что делает UnityMainThreadDispatcher?

1 голос
/ 20 октября 2019

Я прочитал этот код.

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

Вокруг 62-й строки кода приведено следующее описание.

Какая обработка выполняется?

    if (UnityMainThreadDispatcher.Exists()) {
        dispatcher = UnityMainThreadDispatcher.Instance ();
    } 

Есть код на GitHub, но является ли он стандартной функцией в Unity?

1 Ответ

1 голос
/ 20 октября 2019

Вот почему вам нужен псевдо-диспетчер в Unity:

В Unity большинство объектов могут быть созданы только из основного потока Unity.

Но предположим, что у вас есть какая-то тяжелая задача, которую вы хотели бы выполнить в фоновом режиме, например, с помощью Task.Run, в вашей задаче вы не сможете создавать экземпляры таких объектов, как упомянуто выше, но все же хотели бы.

Существует несколько вариантов решения этой проблемы, но все они используют одно и то же:

Захват контекста синхронизации Unity и отправка сообщений в него

Вот оригинальный способ сделать это, вдохновленный Рэймондом Ченом «Старая новая вещь» Рэймонда Чена:

C ++ / WinRT зависть: перенос задач переключения потоков в C # (редакция WPF и WinForms)

Концепция заключается в следующем: в любой момент переключаться на определенный поток в методе!

Открытые типы

IThreadSwitcher:

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

ThreadSwitcher:

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

Личные типы

ThreadSwitcherTask:

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

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

UnityThread:

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

Пример

Хотя это экзотический подход, он обладает огромными преимуществами, а именно: используя один вызов, вы можете выполнять работу в разных потоках одним и тем же методом. Следующий код выполняется с 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);
    }
}

Теперь от вас зависит тонкая нарезка вашей работы, поскольку по натуреКонтекст синхронизации Unity не предназначен для выполнения сложных вычислений, скорее, просто для создания экземпляров, которые вы не смогли бы получить из другого потока.

Простой пример - генерирование некоторой процедурной сетки:

  • выполните все свои математические расчеты в своей задаче и произведите достаточно данных для создания меша
    • , то есть вершин, нормалей, цветов, uvs
  • переключитесь на поток Unity
    • создать сетку из этих данных, PERIOD, это будет достаточно быстро, чтобы быть незаметным

Это был интересный вопрос, надеюсь, я на него ответил!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...