#nullable enable using Cysharp.Threading.Tasks; using System; using System.Collections.Concurrent; using System.Threading; using UnityEngine; namespace UVC.Threading { /// /// Unity 메인 스레드에서 작업을 실행하기 위한 디스패처 클래스입니다. /// /// /// 이 클래스는 싱글톤 패턴으로 구현되어 있으며, 백그라운드 스레드에서 메인 스레드(UI 스레드)로 /// 작업을 디스패치하는 기능을 제공합니다. /// /// /// /// // 백그라운드 스레드에서 UI 업데이트를 해야 하는 경우 (Task 사용) /// await Task.Run(() => /// { /// // 백그라운드 작업 수행 /// var result = ComputeIntensiveTask(); /// /// // UI 업데이트는 메인 스레드에서 수행 (Fire-and-forget) /// MainThreadDispatcher.Instance.SendToMainThread(() => { /// UpdateUIWithResult(result); /// }); /// }); /// /// // UniTask를 사용한 비동기 작업과 메인 스레드 디스패치 예제 /// async UniTask ProcessDataInBackgroundAsync() /// { /// // 스레드풀에서 실행 /// await UniTask.SwitchToThreadPool(); /// /// try /// { /// // 백그라운드에서 시간이 걸리는 작업 수행 /// var data = await FetchDataFromServerAsync(); /// var processedData = ProcessLargeData(data); /// /// // 결과를 메인 스레드로 전달하여 UI 업데이트하고 완료까지 대기 /// await MainThreadDispatcher.Instance.SendToMainThreadAsync(() => { /// // 여기에서 UI 컴포넌트를 안전하게 업데이트 /// UpdateUI(processedData); /// ShowSuccessMessage("데이터 처리 완료"); /// }); /// } /// catch (Exception ex) /// { /// // 오류 발생 시에도 메인 스레드에서 UI 관련 작업 처리 /// await MainThreadDispatcher.Instance.SendToMainThreadAsync(() => { /// ShowErrorDialog($"오류 발생: {ex.Message}"); /// Debug.LogException(ex); /// }); /// } /// finally /// { /// // 작업 완료 후 필요한 경우 다시 메인 스레드로 전환 /// await UniTask.SwitchToMainThread(); /// // 여기서부터는 다시 메인 스레드에서 실행됨 /// CleanupResources(); /// } /// } /// /// // MainThreadDispatcher와 UniTask를 함께 활용한 이벤트 핸들러 예제 /// public async void OnButtonClick() /// { /// // UI 상태 업데이트 /// loadingIndicator.SetActive(true); /// /// try /// { /// // 스레드풀로 전환하여 무거운 작업 수행 /// await UniTask.SwitchToThreadPool(); /// /// // 시간이 걸리는 작업 수행 /// var result = await PerformHeavyComputationAsync(); /// /// // 결과를 UI에 반영하고 완료까지 대기 (메인 스레드에서) /// await MainThreadDispatcher.Instance.SendToMainThreadAsync(() => { /// resultText.text = result.ToString(); /// loadingIndicator.SetActive(false); /// }); /// } /// catch (Exception ex) /// { /// // 예외 처리도 메인 스레드에서 /// await MainThreadDispatcher.Instance.SendToMainThreadAsync(() => { /// errorText.text = ex.Message; /// loadingIndicator.SetActive(false); /// }); /// } /// } /// /// public class MainThreadDispatcher : MonoBehaviour { private static MainThreadDispatcher _instance; private static bool _instantiated; private static SynchronizationContext _mainThreadContext; /// /// MainThreadDispatcher의 단일 인스턴스를 가져옵니다. /// /// /// 인스턴스가 아직 생성되지 않은 경우, 새로운 GameObject를 생성하고 /// 그 GameObject에 MainThreadDispatcher 컴포넌트를 추가합니다. /// public static MainThreadDispatcher Instance { get { if (!_instantiated) { Initialize(); } return _instance; } } /// /// 메인 스레드 디스패처를 초기화합니다. /// private static void Initialize() { if (!_instantiated) { // 이미 존재하는지 확인 _instance = FindFirstObjectByType(); // 없으면 새로 생성 if (_instance == null) { var go = new GameObject("MainThreadDispatcher"); _instance = go.AddComponent(); DontDestroyOnLoad(go); // 씬 전환 시에도 유지 } _instantiated = true; _mainThreadContext = SynchronizationContext.Current; } } private readonly ConcurrentQueue _actionQueue = new ConcurrentQueue(); /// /// 메인 스레드에서 실행할 액션을 큐에 추가합니다. /// /// 메인 스레드에서 실행할 액션 public void Enqueue(Action action) { if (action == null) return; // 현재 스레드가 메인 스레드면 바로 실행 if (Thread.CurrentThread.ManagedThreadId == 1) { action.Invoke(); return; } // 아니면 큐에 추가하여 나중에 실행 _actionQueue.Enqueue(action); } /// /// 메인 스레드에서 액션을 실행합니다. (Fire-and-forget) /// /// 메인 스레드에서 실행할 액션 public void SendToMainThread(Action action) { if (action == null) return; // SynchronizationContext가 있으면 사용 if (_mainThreadContext != null) { _mainThreadContext.Post(_ => action(), null); } else { // SynchronizationContext가 없으면 큐에 추가 Enqueue(action); } } /// /// 메인 스레드에서 액션을 비동기적으로 실행하고, 해당 액션의 완료를 나타내는 UniTask를 반환합니다. /// /// 메인 스레드에서 실행할 액션입니다. /// 액션의 실행이 완료되면 완료되는 UniTask입니다. public UniTask SendToMainThreadAsync(Action action) { if (action == null) return UniTask.CompletedTask; // 현재 스레드가 메인 스레드인 경우 즉시 실행 if (_mainThreadContext != null && SynchronizationContext.Current == _mainThreadContext) { try { action(); return UniTask.CompletedTask; } catch (Exception ex) { return UniTask.FromException(ex); } } var utcs = new UniTaskCompletionSource(); Action wrappedAction = () => { try { action(); utcs.TrySetResult(); } catch (Exception e) { utcs.TrySetException(e); } }; if (_mainThreadContext != null) { _mainThreadContext.Post(_ => wrappedAction(), null); } else { // SynchronizationContext가 없는 경우 큐 사용 Enqueue(wrappedAction); } return utcs.Task; } private void Update() { // 큐에 있는 모든 액션 처리 while (_actionQueue.TryDequeue(out Action action)) { try { action.Invoke(); } catch (Exception e) { Debug.LogException(e); } } } private void Awake() { if (_instance == null) { _instance = this; _mainThreadContext = SynchronizationContext.Current; _instantiated = true; DontDestroyOnLoad(gameObject); } else if (_instance != this) { Destroy(gameObject); } } } }