Оновлення: У 2018 році Unity впроваджує систему робочих завдань C # як спосіб розвантажити роботу та використовувати декілька ядер CPU.
Відповідь нижче передує цій системі. Він все ще працюватиме, але можуть бути кращі варіанти, доступні в сучасному Unity, залежно від ваших потреб. Зокрема, система завдань, як видається, вирішує деякі обмеження щодо того, до чого можуть створюватися потоки, створені вручну, безпечно отримати доступ, описані нижче. Наприклад, розробники експериментують із звітом попереднього перегляду, виконуючи ретрансляції та будуючи сітки паралельно , наприклад.
Я б запропонував користувачам, які мають досвід використання цієї системи завдань, щоб додати власні відповіді, що відображають поточний стан двигуна.
У минулому я використовував нарізки для важких завдань в Unity (зазвичай це обробка зображень та геометрії), і це не відрізняється кардинально, ніж використання ниток в інших додатках C #, з двома застереженнями:
Оскільки Unity використовує дещо старіший підмножину .NET, є деякі новіші функції потоків і бібліотеки, які ми не можемо використовувати поза коробкою, але основи все є.
Як зазначає Альмо у коментарі вище, багато типів Unity не є безпечними для потоків, і вони викинуть винятки, якщо ви спробуєте сконструювати, використати або навіть порівняти їх із головної нитки. Що потрібно пам’ятати:
Один поширений випадок - перевірка того, чи є посилання на GameObject або Monobehaviour нульовими, перш ніж намагатися отримати доступ до своїх учасників. myUnityObject == null
викликає перевантажений оператор за все, що походить з UnityEngine.Object, але System.Object.ReferenceEquals()
працює на цьому певною мірою - просто пам’ятайте, що Destroy () ed GameObject порівнюється з рівним null, використовуючи перевантаження, але ще не ReferenceEqual для null.
Зчитування параметрів з типів Unity зазвичай безпечно для іншого потоку (оскільки воно не одразу ж кине виняток, якщо ви обережно перевіряєте наявність нулів, як зазначено вище), але зверніть увагу на попередження Філіпа, що головний потік може змінювати стан поки ти її читаєш. Вам потрібно буде бути дисциплінованим щодо того, кому дозволено змінювати те, що і коли, щоб уникнути читання непослідовних станів, що може призвести до помилок, які можна начебто важко відстежити, оскільки вони залежать від підмілісекундних моментів між потоками, які ми можемо не відтворювати за бажанням
Випадкові та часові статичні члени не доступні. Створіть примірник System.Random за нитку, якщо вам потрібна випадковість, і System.Diagnostics.Stopwatch, якщо вам потрібна інформація про час.
Функції Mathf, вектор, матриця, кватерніон та кольори всі добре працюють в нитках, тому ви можете робити більшість своїх обчислень окремо
Створення GameObjects, приєднання Monobehaviours або створення / оновлення текстур, сіток, матеріалів тощо, все це повинно відбуватися в основному потоці. Раніше, коли мені потрібно було працювати з ними, я створив чергу виробників-споживачів, де мій робочий потік готує вихідні дані (наприклад, великий масив векторів / кольорів для застосування до сітки чи текстури), а оновлення або корегування на головному потоці опитує дані та застосовує їх.
Якщо ці замітки не виходять, ось такий шаблон, який я часто використовую для потокової роботи. Я не даю жодних гарантій, що це стиль найкращої практики, але він виконує роботу. (Коментарі або редагування для покращення вітаються. Я знаю, що нитка є дуже глибокою темою, про яку я знаю лише основи)
using UnityEngine;
using System.Threading;
public class MyThreadedBehaviour : MonoBehaviour
{
bool _threadRunning;
Thread _thread;
void Start()
{
// Begin our heavy work on a new thread.
_thread = new Thread(ThreadedWork);
_thread.Start();
}
void ThreadedWork()
{
_threadRunning = true;
bool workDone = false;
// This pattern lets us interrupt the work at a safe point if neeeded.
while(_threadRunning && !workDone)
{
// Do Work...
}
_threadRunning = false;
}
void OnDisable()
{
// If the thread is still running, we should shut it down,
// otherwise it can prevent the game from exiting correctly.
if(_threadRunning)
{
// This forces the while loop in the ThreadedWork function to abort.
_threadRunning = false;
// This waits until the thread exits,
// ensuring any cleanup we do after this is safe.
_thread.Join();
}
// Thread is guaranteed no longer running. Do other cleanup tasks.
}
}
Якщо вам не потрібно чітко розбивати роботу на нитки для швидкості, і ви просто шукаєте спосіб зробити це не блокуючим, щоб решта вашої гри продовжувала тикати, легше рішення в Unity - це Coroutines . Це функції, які можуть виконати певну роботу, а потім повернути двигун назад до двигуна, щоб продовжити те, що він робить, і відновити роботу безперервно в наступний час.
using UnityEngine;
using System.Collections;
public class MyYieldingBehaviour : MonoBehaviour
{
void Start()
{
// Begin our heavy work in a coroutine.
StartCoroutine(YieldingWork());
}
IEnumerator YieldingWork()
{
bool workDone = false;
while(!workDone)
{
// Let the engine run for a frame.
yield return null;
// Do Work...
}
}
}
Це не потребує особливих міркувань щодо очищення, оскільки двигун (наскільки я можу сказати) позбавляється від супротивників зруйнованих для вас об’єктів.
Весь локальний стан методу зберігається, коли він отримує та поновлюється, тому для багатьох цілей це як би він працював безперебійно на іншій потоці (але у вас є всі зручності роботи на головній нитці). Потрібно просто переконатися, що кожна ітерація її є достатньо короткою, щоб вона не збиралася гальмувати необґрунтовано основну нитку.
Переконуючись, що важливі операції не розділяються на вихід, ви можете отримати послідовність однопотокової поведінки - знаючи, що жоден інший скрипт або система в основному потоці не можуть змінювати дані, над якими ви працюєте в середині.
Лінія повернення врожаю дає кілька варіантів. Ти можеш...
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
є екземпляр Coroutine , щоб відновити його після otherCoroutine
завершення. Це часто використовується у формі yield return StartCoroutine(OtherCoroutineMethod())
для ланцюжка виконання нової програми, яка сама може отримати результат, коли цього захоче.
Експериментально, пропуск другого StartCoroutine
та просто написання yield return OtherCoroutineMethod()
досягає тієї ж мети, якщо ви хочете ланцюжок виконання у тому ж контексті.
Обгортання всередині StartCoroutine
може все-таки бути корисним, якщо ви хочете запустити вкладену програму в поєднанні з другим об'єктом, наприкладyield return otherObject.StartCoroutine(OtherObjectsCoroutineMethod())
... залежно від того, коли ви хочете, щоб наступна черга поправки відбулася.
Або yield break;
припинити роботу програми, перш ніж вона досягне кінця, як ви можете використовувати return;
для раннього використання звичайного методу.