← Назад к вопросам

Как использовать потоки (threads) в Unity? Можно ли модифицировать GameObject из другого потока?

1.3 Junior🔥 111 комментариев
#Unity Core

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI7 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Использование потоков (threads) в Unity и ограничения

В Unity потоки (threads) используются для выполнения тяжелых вычислений, операций с файлами, сетевых запросов или других задач, которые могут блокировать основной поток игры и вызывать падение FPS. Однако взаимодействие с движком Unity и его объектами из сторонних потоков строго ограничено.

Основные принципы работы с потоками в Unity

Для работы с потоками в C# в Unity используются классы из пространства имен System.Threading, такие как Thread, ThreadPool, или более современные подходы с Task (из TPL). Вот базовый пример создания и запуска потока:

using System.Threading;
using UnityEngine;

public class ThreadExample : MonoBehaviour
{
    private Thread calculationThread;

    void Start()
    {
        // Создаем поток для выполнения тяжелых вычислений
        calculationThread = new Thread(PerformComplexCalculation);
        calculationThread.Start();
    }

    void PerformComplexCalculation()
    {
        // Это выполняется в отдельном потоке
        for (int i = 0; i < 1000000; i++)
        {
            // Математические вычисления, обработка данных
            float result = Mathf.Sqrt(i) * Mathf.Log(i);
        }
        Debug.Log("Calculation finished in thread.");
    }

    void OnDestroy()
    {
        // Важно: завершаем поток при уничтожении объекта
        if (calculationThread != null && calculationThread.IsAlive)
        {
            calculationThread.Join();
        }
    }
}

Можно ли модифицировать GameObject из другого потока?

НЕТ, напрямую модифицировать GameObject или любой компонент Unity (Transform, Renderer, Rigidbody и т.д.) из другого потока НЕЛЬЗЯ. Это фундаментальное ограничение архитектуры Unity. Движок Unity (включая внутренние системы трансформаций, рендеринга и физики) работает исключительно в главном потоке (main thread).

Попытка изменить свойство из другого потока приведет к ошибке или неопределенному поведению:

// НЕПРАВИЛЬНО: этот код вызовет проблемы
void WrongThreadModification()
{
    Thread thread = new Thread(() =>
    {
        // Попытка модифицировать GameObject из другого потока
        GameObject.Find("Player").transform.position = new Vector3(10, 0, 0); // ОШИБКА!
    });
    thread.Start();
}

Правильный подход: взаимодействие с Unity из многопоточного кода

Для передачи результатов вычислений из стороннего потока в основной используется один из следующих механизмов:

  1. Очередь действий (Action Queue): результаты или команды накапливаются в потокобезопасной коллекции в стороннем потоке, а затем выполняются в главном потоке, например, через Update().
using System.Collections.Concurrent;
using System.Threading;
using UnityEngine;

public class SafeThreadCommunication : MonoBehaviour
{
    private Thread workerThread;
    private ConcurrentQueue<System.Action> actionQueue = new ConcurrentQueue<System.Action>();

    void Start()
    {
        workerThread = new Thread(WorkerThreadFunction);
        workerThread.Start();
    }

    void WorkerThreadFunction()
    {
        // Вычисления в стороннем потоке
        Vector3 calculatedPosition = CalculateNewPosition();

        // Записываем команду для главного потока, не выполняя ее здесь
        actionQueue.Enqueue(() =>
        {
            // Это выполнится в главном потоке
            transform.position = calculatedPosition;
        });
    }

    void Update()
    {
        // В главном потоке проверяем и выполняем накопленные действия
        while (actionQueue.TryDequeue(out System.Action action))
        {
            action.Invoke();
        }
    }

    Vector3 CalculateNewPosition()
    {
        // Тяжелые вычисления
        return new Vector3(1, 2, несколько миллионов операций...);
    }
}
  1. Использование UnityEngine.Dispatcher или аналогичных паттернов (например, через MainThreadDispatcher из сторонних библиотек).

  2. Современный подход с async/await и UniTask (для Unity часто используют библиотеку UniTask). Она предоставляет более удобные средства для асинхронных операций и возврата в главный поток.

using Cysharp.Threading.Tasks;
using UnityEngine;

public class AsyncExample : MonoBehaviour
{
    async void Start()
    {
        // Выполняем тяжелую работу в пуле потоков
        Vector3 result = await Task.Run(() => CalculateHeavyData());

        // После await мы автоматически возвращаемся в главный поток Unity
        transform.position = result; // Безопасно!
    }

    Vector3 CalculateHeavyData()
    {
        // Расчеты в другом потоке
        return new Vector3(5, 0, 0);
    }
}

Ключевые рекомендации по использованию потоков в Unity

  • Главный поток только для Unity API: Все вызовы методов Unity (Instantiate, Destroy, GetComponent, изменение transform, material и т.д.) должны выполняться в главном потоке.
  • Потоки для вычислений: Используйте потоки только для независимых вычислений, обработки данных, загрузки файлов, сетевых операций.
  • Синхронизация данных: Используйте потокобезопасные коллекции (ConcurrentQueue, ConcurrentBag) или механизмы синхронизации (lock, Mutex) для обмена данными между потоками.
  • Осторожность с завершением: Убедитесь, что все сторонние потоки правильно завершаются при остановке приложения или уничтожении объектов (OnDestroy()).
  • Производительность: Создание большого количества потоков может быть накладным. Для кратких задач рассмотрите ThreadPool или Task.Run.

Итог: Потоки в Unity — мощный инструмент для оптимизации, но их использование требует строгого соблюдения правила разделения ответственности: вычисления в сторонних потоках, визуализация и управление объектами — только в главном потоке.