Posts Main Thread Dispatcher
Post
Cancel

Main Thread Dispatcher

메인 스레드 디스패처?


유니티 엔진에서는 메인 스레드가 아닌 다른 스레드에서

게임오브젝트, 트랜스폼 등 유니티 API에 접근할 수 없게 제한되어 있다.

하지만 메인 스레드 디스패처를 사용하면 이 문제를 해결할 수 있다.


동작 원리

  • 메인 스레드 디스패처에는 동기화 큐(Queue)가 존재한다.

  • 다른 스레드에서 유니티 API 작업이 필요할 경우, 메인 스레드 디스패처의 큐에 집어 넣는다.

  • 메인 스레드 디스패처는 매 프레임마다 큐에서 작업을 꺼내어 메인 스레드 내에서 수행한다.

  • 다른 스레드에서는 위와 같이 디스패처를 통해 메인 스레드에 작업을 위임하게 되어, 결과적으로 안전하게 메인스레드 내에서 유니티 API 작업을 처리할 수 있게 된다.


사용 예시


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
private MainThreadDispatcher mtd;

private void Start()
{
    mtd = MainThreadDispatcher.Instance;
    Task.Run(() => TestBody());
}

private async void TestBody()
{
    int res1 = -1, res2 = -1;

    // 1. 비동기 요청
    mtd.Request(() => res1 = Random.Range(0, 1000));
    Debug.Log(res1);

    // 2. await를 통한 대기 - Action
    await mtd.RequestAsync(() => { res2 = Random.Range(0, 1000); });
    Debug.Log(res2);

    // 3. await를 통한 대기 - Func<int>
    Task<int> resultTask = mtd.RequestAsync(() => Random.Range(0, 1000));
    await resultTask;
    Debug.Log(resultTask.Result);
}


소스코드


.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
/*
    Copyright 2015 Pim de Witte All Rights Reserved.

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

        http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.

    https://github.com/PimDeWitte/UnityMainThreadDispatcher
*/

// 날짜 : 2021-06-30 AM 2:56:52

using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

using MTD = MainThreadDispatcher;

public class MainThreadDispatcher : MonoBehaviour
{
    /***********************************************************************
    *                               Singleton
    ***********************************************************************/
    #region .

    private static MTD _instance;
    public static MTD Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = FindObjectOfType<MTD>();

                if (_instance == null)
                {
                    GameObject container = new GameObject($"Main Thread Dispatcher");
                    _instance = container.AddComponent<MTD>();
                }
            }

            return _instance;
        }
    }

    private void Awake()
    {
        if (_instance == null)
        {
            _instance = this;
            transform.SetParent(null);
            DontDestroyOnLoad(this);
        }
        else
        {
            if (_instance != this)
            {
                if (GetComponents<Component>().Length <= 2)
                    Destroy(gameObject);
                else
                    Destroy(this);
            }
        }
    }

    #endregion

    private static readonly Queue<Action> _executionQueue = new Queue<Action>();

    private void Update()
    {
        lock (_executionQueue)
        {
            while (_executionQueue.Count > 0)
            {
                _executionQueue.Dequeue().Invoke();
            }
        }
    }

    /// <summary> 메인 스레드에 작업 요청(코루틴) </summary>
    public void Request(IEnumerator coroutine)
    {
        lock (_executionQueue)
        {
            _executionQueue.Enqueue(() =>
            {
                StartCoroutine(coroutine);
            });
        }
    }

    /// <summary> 메인 스레드에 작업 요청(메소드) </summary>
    public void Request(Action action)
    {
        Request(ActionWrapper(action));
    }

    /// <summary> 메인 스레드에 작업 요청 및 대기(await) </summary>
    public Task RequestAsync(Action action)
    {
        var tcs = new TaskCompletionSource<bool>();

        void WrappedAction()
        {
            try
            {
                action();
                tcs.TrySetResult(true);
            }
            catch (Exception ex)
            {
                tcs.TrySetException(ex);
            }
        }

        Request(ActionWrapper(WrappedAction));
        return tcs.Task;
    }

    /// <summary> 메인 스레드에 작업 요청 및 대기(await) + 값 받아오기 </summary>
    public Task<T> RequestAsync<T>(Func<T> action)
    {
        var tcs = new TaskCompletionSource<T>();

        void WrappedAction()
        {
            try
            {
                var result = action();
                tcs.TrySetResult(result);
            }
            catch (Exception ex)
            {
                tcs.TrySetException(ex);
            }
        }

        Request(ActionWrapper(WrappedAction));
        return tcs.Task;
    }

    /// <summary> Action을 코루틴으로 래핑 </summary>
    private IEnumerator ActionWrapper(Action a)
    {
        a();
        yield return null;
    }
}


References


This post is licensed under CC BY 4.0 by the author.