반응형
Unity에서 외부 Thread를 만들어 사용할 때 유니티 관련 처리는 유니티 Main Thread에서 처리해야한다.
- 유니티 내에서 외부 Thread를 사용해 TCP/UDP 서버가 동작하도록 만들었는데, 내부에서 유니티 관련 함수를 하니 빌드해서 테스트 했을 때 간혹 빌드본이 응답없음이 되면서 잘못된 주소를 참조했습니다.라는 오류가 떴다. 그래서 알아보니 유니티에서 외부 Thread를 사용할 때, 유니티의 GameObject나 Transform을 변경하거나 GetComponent 등의 함수를 호출했을 때 그런 문제가 발생할 수 있다고 한다.
- 그래서 이런 문제를 해결하기 위해 해당 함수를 유니티 Main Thread로 가져와 사용하는 방식을 사용해서 해결했고, 그 방식은 아래와 같이 외부에서 사용되는 함수를 Action에 담아 큐에 넣어두고 유니티 Update 함수에서 순차적으로 큐에서 뽑아서 사용했다.
예시 코드
// 메인스레드 처리 큐
private readonly Queue<Action> _mainThreadActions = new Queue<Action>();
void Update()
{
// 실행해야될 함수 배열
Action[] actionsCopy;
// lock을 이용해 다른 Thread에서 큐에 추가할 때 호출되지 않도록 충돌 방지
// -> Enqueue와 같은 lock을 사용
lock (_mainThreadActions)
{
// 받아온 모든 함수를 복사
actionsCopy = _mainThreadActions.ToArray();
// 기존 큐는 clear
_mainThreadActions.Clear();
}
// 복사한 배열을 돌면서 순차적으로 실행함
foreach (var action in actionsCopy)
{
try
{
// null을 방지하면서 안전하게 실행
action?.Invoke();
}
catch (Exception e)
{
Debug.LogError("Unity Main Thread 실행 중 오류 발생");
}
}
}
/// <summary>
/// Unity Main Thread에서 사용되어야 할 함수를 Main Thread 처리 큐에 넣어주는 함수
/// </summary>
/// <param name="action"></param>
public void MainThreadEnqueue(Action action)
{
// lock을 이용해 다른 Thread에서 큐에 추가할 때 호출되지 않도록 충돌 방지
// -> Update의 큐 복사할 때와 같은 lock을 사용
lock (_mainThreadActions)
{
// 큐 오버플로우 방지
if (_mainThreadActions.Count > 50)
{
Debug.LogWarning("MainThread 큐 오버플로우, 오래된 명령 버림");
_mainThreadActions.Dequeue();
}
// Main Thread 처리 큐에 현재 함수를 넣음
_mainThreadActions.Enqueue(action);
}
}
List를 이용한 변형 구조(GC 성능 개선) - Chat GPT 질문
using System;
using System.Collections.Generic;
using UnityEngine;
public class MainThreadDispatcher : MonoBehaviour
{
// 대기 중인 작업 (외부 스레드에서 들어옴)
private readonly List<Action> _pendingActions = new List<Action>();
// 실행할 작업 (Update에서 실행)
private readonly List<Action> _currentActions = new List<Action>();
// 리스트 보호용 lock
private readonly object _lockObj = new object();
void Update()
{
// 스왑을 통해 복사 없이 안전하게 리스트 교체
lock (_lockObj)
{
var temp = _currentActions;
_currentActions.Clear();
_currentActions.AddRange(_pendingActions);
_pendingActions.Clear();
}
// 스왑된 리스트 실행
foreach (var action in _currentActions)
{
try
{
action?.Invoke();
}
catch (Exception e)
{
Debug.LogError($"Unity Main Thread 실행 중 오류 발생 : {e.Message}");
}
}
}
/// <summary>
/// 외부 스레드에서 메인 스레드로 실행할 작업을 큐에 넣음
/// </summary>
public void Enqueue(Action action)
{
if (action == null) return;
lock (_lockObj)
{
// 큐가 너무 커지는 걸 방지 (예: 100개 제한)
if (_pendingActions.Count > 100)
{
Debug.LogWarning("[MainThreadDispatcher] 큐 오버플로우 - 새 작업 무시됨");
return;
}
_pendingActions.Add(action);
}
}
}