Потокобезопасность: дерево Entity, данные и владение
Что оставить в главном потоке, что передать worker и почему mutex или CallDeferred не решают всё.
Практическая граница
Для обычного C# Task или worker оставляйте активное дерево Entity и интерфейс в главном потоке. Передавайте вычислению самостоятельный снимок входа, возвращайте самостоятельный результат и применяйте его после проверки актуальности. Полный пример делает это через _Process.
| Объект/операция | Правило для примера и обычного C# worker |
|---|---|
| Entity в активном дереве, AddChild, удаление, Label/Text и игровое состояние | Главный поток; не захватывайте их в вычисляющую лямбду |
| Числа, строки, собственный массив с исключительным владельцем | Можно передать worker; после передачи не менять те же данные с другого потока |
| Общий List/Dictionary, изменение размера коллекции | Нужен согласованный протокол блокировки или подходящая concurrent-коллекция; обычная коллекция сама его не даёт |
| Resource, материал, текстура, PackedEntity | Ссылка может указывать на общий объект; копия ссылки не является независимой копией данных |
| Нативный server API, загрузчик, рендеринг и физика | Проверяйте конкретный метод, backend и конфигурацию; общего разрешения «все singleton потокобезопасны» нет |
Главный поток в этой таблице не означает, что движок вообще не имеет других режимов обработки. ProcessThreadGroup и внутренние потоки подсистем имеют собственные правила. Обычный Task.Run не входит в разрешённую группу обработки Entity только потому, что выполняется в пуле.
Почему lock недостаточно
lock защищает только тот доступ к вашим данным, при котором все участники берут тот же lock. Он не меняет требование движка вызывать AddChild из главного потока и не заставляет рендерер брать ваш mutex. В стенде реальный вызов AddChild к активному дереву из .NET worker отклоняется движком; проверяется отсутствие добавленного ребёнка.
Отсутствие ошибки в другой сборке не доказывает безопасность: диагностические проверки могут зависеть от конфигурации. Не отключайте проверки потока ради подавления сообщения. Ошибку устраняют изменением владельца и места выполнения операции.
Не держите lock, пока ждёте другую задачу или главный поток. Если главный поток ждёт worker, а worker ждёт действие главного, получается взаимная блокировка. Отмена не может завершить такой цикл, если код не проверяет её или заблокирован на бесконечном ожидании.
Передача результата
CallDeferred ставит вызов в очередь на более позднюю обработку; это не завершённая операция и не обещание определённого следующего кадра. Он не копирует произвольный изменяемый C#-объект и не продлевает жизнь всех ссылок, захваченных вашей лямбдой. Даже при отложенном применении нужны действительный получатель, владелец результата и правило отбрасывания старого запроса.
В BackgroundJob результат читается только после завершения Task в _Process. При выходе из дерева задача отменяется, а её продолжение не трогает Entity. PoolPrimeBatch копирует вход и публикует выход только после join. Это конкретные правила владения, а не проверка существования объекта один раз перед работой на другом потоке.
Параллельная запись в разные заранее выделенные индексы массива может быть частью корректного протокола, если индексы не пересекаются, массив не меняет размер, результат не читают до завершения и ошибки учтены. Именно так устроена группа примера. Это не разрешение одновременно менять элементы общей коллекции Resource или свойства чужих Entity.
Ресурсы и тяжёлая подготовка
Для загрузки используйте предназначенный для этого API загрузчика, затем создавайте и добавляйте Entity по правилам его жизненного цикла. Не помещайте Instantiate, изменение общего материала и AddChild в Task.Run по аналогии с чистым вычислением чисел. Даже объект вне дерева может ссылаться на общие ресурсы и вызвать серверные операции.
Переносите только подтверждённо независимую часть. Нативная потокобезопасность выбранного метода не отменяет ваши гонки данных, а managed concurrent-коллекция не делает безопасным содержащийся в ней нативный объект. Экспорт, другие backend и специальные группы обработки требуют отдельной проверки.