ThreadUtility.cs 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. #if ENABLE_MONO && (DEVELOPMENT_BUILD || UNITY_EDITOR)
  2. using System;
  3. using System.Runtime.CompilerServices;
  4. using System.Threading;
  5. using UnityEngine;
  6. namespace SingularityGroup.HotReload {
  7. #if UNITY_EDITOR
  8. [UnityEditor.InitializeOnLoad]
  9. #endif
  10. static class ThreadUtility {
  11. /// <summary>
  12. /// Run code on Unity's main thread
  13. /// </summary>
  14. /// <remarks>
  15. /// This field is set early in [InitializeOnLoadMethod] in the editor and [RuntimeInitializeOnLoad] in playmode / for player builds, so your code assume it is already set.
  16. /// </remarks>
  17. #if UNITY_EDITOR
  18. static SynchronizationContext _cachedMainContext;
  19. public static SynchronizationContext MainContext
  20. {
  21. get {
  22. if(_cachedMainContext != null) {
  23. return _cachedMainContext;
  24. }
  25. return EditorFallbackContext.I;
  26. }
  27. private set {
  28. _cachedMainContext = value;
  29. }
  30. }
  31. class EditorFallbackContext : SynchronizationContext {
  32. public static readonly EditorFallbackContext I = new EditorFallbackContext();
  33. EditorFallbackContext() { }
  34. public override void Send(SendOrPostCallback d, object state) {
  35. UnityEditor.EditorApplication.delayCall += () => d(state);
  36. }
  37. public override void Post(SendOrPostCallback d, object state) {
  38. UnityEditor.EditorApplication.delayCall += () => d(state);
  39. }
  40. }
  41. #else
  42. public static SynchronizationContext MainContext {get; private set;}
  43. #endif
  44. public static int mainThreadId {get; private set;}
  45. #if UNITY_EDITOR
  46. static ThreadUtility() {
  47. #else
  48. [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
  49. static void InitMainThread() {
  50. #endif
  51. MainContext = SynchronizationContext.Current;
  52. mainThreadId = Thread.CurrentThread.ManagedThreadId;
  53. }
  54. [MethodImpl(MethodImplOptions.NoInlining)]
  55. public static void InitEditor() {
  56. //trigger static constructor
  57. }
  58. public static bool ShouldLogException(Exception ex) {
  59. AggregateException agg;
  60. while((agg = ex as AggregateException) != null) {
  61. ex = agg.InnerException;
  62. }
  63. if(ex is ThreadAbortException) {
  64. return false;
  65. }
  66. return true;
  67. }
  68. public static void LogException(Exception ex, CancellationToken token = default(CancellationToken)) {
  69. if(ShouldLogException(ex) && !token.IsCancellationRequested) {
  70. Log.Exception(ex);
  71. }
  72. }
  73. public static void RunOnMainThread(Action action, CancellationToken token = default(CancellationToken)) {
  74. if(Thread.CurrentThread.ManagedThreadId == mainThreadId) {
  75. action();
  76. } else {
  77. MainContext.Post(_ => {
  78. if(!token.IsCancellationRequested) {
  79. action();
  80. }
  81. }, null);
  82. }
  83. }
  84. public static SwitchToMainThreadAwaitable SwitchToMainThread() {
  85. return new SwitchToMainThreadAwaitable();
  86. }
  87. public static CancellableSwitchToMainThreadAwaitable SwitchToMainThread(CancellationToken token) {
  88. return new CancellableSwitchToMainThreadAwaitable(token);
  89. }
  90. public static SwitchToThreadPoolAwaitable SwitchToThreadPool() {
  91. return new SwitchToThreadPoolAwaitable();
  92. }
  93. public static CancellableSwitchToThreadPoolAwaitable SwitchToThreadPool(CancellationToken token) {
  94. return new CancellableSwitchToThreadPoolAwaitable(token);
  95. }
  96. }
  97. struct SwitchToMainThreadAwaitable {
  98. public Awaiter GetAwaiter() => new Awaiter();
  99. public struct Awaiter : ICriticalNotifyCompletion {
  100. static readonly SendOrPostCallback switchToCallback = Callback;
  101. public bool IsCompleted => Thread.CurrentThread.ManagedThreadId == ThreadUtility.mainThreadId;
  102. public void GetResult() { }
  103. public void OnCompleted(Action continuation) {
  104. ThreadUtility.MainContext.Post(switchToCallback, continuation);
  105. }
  106. public void UnsafeOnCompleted(Action continuation) {
  107. ThreadUtility.MainContext.Post(switchToCallback, continuation);
  108. }
  109. static void Callback(object state) {
  110. var continuation = (Action)state;
  111. continuation();
  112. }
  113. }
  114. }
  115. struct CancellableSwitchToMainThreadAwaitable {
  116. readonly CancellationToken token;
  117. public CancellableSwitchToMainThreadAwaitable(CancellationToken token) {
  118. this.token = token;
  119. }
  120. public Awaiter GetAwaiter() => new Awaiter(token);
  121. public struct Awaiter : ICriticalNotifyCompletion {
  122. readonly CancellationToken token;
  123. public Awaiter(CancellationToken token) {
  124. this.token = token;
  125. }
  126. public bool IsCompleted => Thread.CurrentThread.ManagedThreadId == ThreadUtility.mainThreadId;
  127. public void GetResult() { }
  128. public void OnCompleted(Action continuation) {
  129. UnsafeOnCompleted(continuation);
  130. }
  131. public void UnsafeOnCompleted(Action continuation) {
  132. var tokenCopy = this.token;
  133. ThreadUtility.MainContext.Post(o => {
  134. if(!tokenCopy.IsCancellationRequested) {
  135. continuation();
  136. }
  137. }, null);
  138. }
  139. }
  140. }
  141. struct CancellableSwitchToThreadPoolAwaitable {
  142. readonly CancellationToken token;
  143. public CancellableSwitchToThreadPoolAwaitable(CancellationToken token) {
  144. this.token = token;
  145. }
  146. public Awaiter GetAwaiter() => new Awaiter(token);
  147. public struct Awaiter : ICriticalNotifyCompletion {
  148. readonly CancellationToken token;
  149. public Awaiter(CancellationToken token) {
  150. this.token = token;
  151. }
  152. public bool IsCompleted => false;
  153. public void GetResult() { }
  154. public void OnCompleted(Action continuation) {
  155. ThreadPool.UnsafeQueueUserWorkItem(Callback, continuation);
  156. }
  157. public void UnsafeOnCompleted(Action continuation) {
  158. ThreadPool.UnsafeQueueUserWorkItem(Callback, continuation);
  159. }
  160. void Callback(object state) {
  161. token.ThrowIfCancellationRequested();
  162. var continuation = (Action)state;
  163. continuation();
  164. }
  165. }
  166. }
  167. struct SwitchToThreadPoolAwaitable {
  168. public Awaiter GetAwaiter() => new Awaiter();
  169. public struct Awaiter : ICriticalNotifyCompletion {
  170. static readonly WaitCallback switchToCallback = Callback;
  171. public bool IsCompleted => false;
  172. public void GetResult() { }
  173. public void OnCompleted(Action continuation) {
  174. ThreadPool.UnsafeQueueUserWorkItem(switchToCallback, continuation);
  175. }
  176. public void UnsafeOnCompleted(Action continuation) {
  177. ThreadPool.UnsafeQueueUserWorkItem(switchToCallback, continuation);
  178. }
  179. static void Callback(object state) {
  180. var continuation = (Action)state;
  181. continuation();
  182. }
  183. }
  184. }
  185. }
  186. #endif