У меня есть алгоритм генерации уровня, который является тяжелым в вычислительном отношении. Таким образом, его вызов всегда приводит к зависанию экрана игры. Как я могу поместить функцию во второй поток, пока игра все еще продолжает отображать экран загрузки, чтобы показать, что игра не заморожена?
unity
multithreading
DarkDestry
источник
источник
List
действий для хранения функций, которые вы хотите вызвать в главном потоке. заблокировать и скопироватьAction
список вUpdate
функции во временный список, очистить исходный список и выполнитьAction
код вList
основном потоке. Смотрите UnityThread из моего другого поста о том, как это сделать. Например, чтобы вызвать функцию в главном потоке,UnityThread.executeInUpdate(() => { transform.Rotate(new Vector3(0f, 90f, 0f)); });
Ответы:
Обновление: в 2018 году Unity внедряет систему заданий C # как способ разгрузить работу и использовать несколько ядер ЦП.
Ответ ниже предшествует этой системе. Это все еще будет работать, но в современном Unity могут быть и другие варианты, в зависимости от ваших потребностей. В частности, система заданий, по-видимому, устраняет некоторые ограничения на то, что потоки, созданные вручную, могут безопасно получить доступ, описанные ниже. Например, разработчики экспериментируют с отчетом о предварительном просмотре, выполняя raycast и создавая сетки параллельно .
Я бы пригласил пользователей с опытом работы с этой системой заданий добавить свои собственные ответы, отражающие текущее состояние движка.
В прошлом я использовал многопоточность для тяжеловесных задач в Unity (обычно это обработка изображений и геометрии), и она не сильно отличается от использования потоков в других приложениях C # с двумя оговорками:
Поскольку в Unity используется несколько более старое подмножество .NET, есть некоторые новые функции и библиотеки потоков, которые мы не можем использовать "из коробки", но здесь есть основы.
Как отмечает Almo в комментарии выше, многие типы Unity не являются потокобезопасными и будут генерировать исключения, если вы попытаетесь сконструировать, использовать или даже сравнить их из основного потока. Что нужно иметь в виду:
Один из распространенных случаев - проверка на наличие нулевой ссылки GameObject или Monobehaviour, прежде чем пытаться получить доступ к его членам.
myUnityObject == null
вызывает перегруженный оператор для всего, что происходит от UnityEngine.Object, ноSystem.Object.ReferenceEquals()
работает в некоторой степени вокруг этого - просто помните, что редактируемый GameObject Destroy () сравнивается как равный null, используя перегрузку, но еще не ReferenceEqual для null.Чтение параметров из типов Unity обычно безопасно в другом потоке (в том смысле, что оно не вызовет немедленного исключения, если вы будете тщательно проверять наличие нулевых значений, как указано выше), но обратите внимание на предупреждение Филиппа о том, что основной поток может изменять состояние пока ты читаешь это. Вы должны будете быть дисциплинированными в отношении того, кому разрешено изменять что и когда, чтобы избежать чтения какого-либо противоречивого состояния, которое может привести к ошибкам, которые могут быть чертовски трудно отследить, поскольку они зависят от временных интервалов между нитями, которые мы можем Воспроизведение по желанию.
Случайные и статические члены Time недоступны. Создайте экземпляр System.Random для каждого потока, если вам нужна случайность, и System.Diagnostics.Stopwatch, если вам нужна информация о времени.
Все функции Mathf, Vector, Matrix, Quaternion и Color хорошо работают между потоками, поэтому вы можете выполнять большинство вычислений отдельно.
Создание GameObjects, присоединение Monobehaviours или создание / обновление текстур, сеток, материалов и т. Д. - все это должно происходить в главном потоке. В прошлом, когда мне нужно было работать с ними, я настраивал очередь производителя-потребителя, где мой рабочий поток подготавливает необработанные данные (например, большой массив векторов / цветов для применения к сетке или текстуре), и обновление или сопрограмма в главном потоке опрашивает данные и применяет их.
С этими заметками, вот шаблон, который я часто использую для многопоточной работы. Я не гарантирую, что это стиль наилучшей практики, но он выполняет свою работу. (Комментарии или изменения для улучшения приветствуются - я знаю, что многопоточность - очень глубокая тема, из которой я знаю только основы)
Если вам не нужно строго распределять работу между потоками для ускорения, и вы просто ищете способ сделать его неблокирующим, чтобы остальная часть игры продолжала работать, более легким решением в Unity является Coroutines . Это функции, которые могут выполнять некоторую работу, а затем возвращать управление движку для продолжения работы и возобновления работы в более позднее время.
Для этого не нужны особые соображения по очистке, поскольку движок (насколько я могу судить) избавляет вас от сопрограмм от разрушенных объектов.
Все локальное состояние метода сохраняется при его выходе и возобновлении, поэтому для многих целей он работает так, как будто он работает непрерывно в другом потоке (но у вас есть все удобства работы в основном потоке). Вам просто нужно убедиться, что каждая его итерация достаточно коротка, чтобы не замедлять основной поток.
Убедившись, что важные операции не разделены выходом, вы можете получить согласованность однопоточного поведения - зная, что никакой другой скрипт или система в основном потоке не может изменить данные, над которыми вы работаете.
Линия возврата урожая дает вам несколько вариантов. Вы можете...
yield return null
возобновить после обновления следующего кадра ()yield return new WaitForFixedUpdate()
возобновить после следующего FixedUpdate ()yield return new WaitForSeconds(delay)
возобновить игру по истечении определенного количества игрового времениyield return new WaitForEndOfFrame()
возобновить после того, как GUI закончит рендерингyield return myRequest
гдеmyRequest
это WWW экземпляр, чтобы возобновить когда запрошенные данные после загрузки из Интернета или диска.yield return otherCoroutine
гдеotherCoroutine
это экземпляр сопрограммная , чтобы возобновить послеotherCoroutine
Завершает. Это часто используется в форме,yield return StartCoroutine(OtherCoroutineMethod())
чтобы связать выполнение с новой сопрограммой, которая сама может дать результат, когда захочет.Экспериментально, пропуская вторую
StartCoroutine
и простую запись, можноyield return OtherCoroutineMethod()
достичь одной и той же цели, если вы хотите связать выполнение в одном контексте.Обтекание внутри
StartCoroutine
может по-прежнему быть полезным, если вы хотите запустить вложенную сопрограмму вместе со вторым объектом, напримерyield return otherObject.StartCoroutine(OtherObjectsCoroutineMethod())
... в зависимости от того, когда вы хотите, чтобы сопрограмма сделала следующий ход.
Или
yield break;
остановить сопрограмму до того, как она достигнет конца, как вы могли бы использоватьreturn;
для раннего выхода из обычного метода.источник
Вы можете поместить свои тяжелые вычисления в другой поток, но API Unity не является потокобезопасным, их нужно выполнять в основном потоке.
Хорошо, вы можете попробовать этот пакет в Asset Store, который поможет вам легче использовать потоки. http://u3d.as/wQg Вы можете просто использовать только одну строку кода для запуска потока и безопасного выполнения Unity API.
источник
Используя Svelto.Tasks, вы можете довольно легко вернуть результат многопоточной подпрограммы в основной поток (и, следовательно, функции единства):
http://www.sebaslab.com/svelto-taskrunner-run-serial-and-parallel-asynchronous-tasks-in-unity3d/
источник
@DMGregory объяснил это очень хорошо.
Можно использовать как потоки, так и сопрограммы. Сначала разгрузить основной поток, а затем вернуть управление главному потоку. Толкание тяжестей, чтобы отделить нить, кажется более разумным. Поэтому очереди заданий могут соответствовать вашим ожиданиям.
В Unity Wiki есть действительно хороший пример скрипта JobQueue .
источник