|
6 | 6 | using System.Threading; |
7 | 7 | using System.Threading.Tasks; |
8 | 8 |
|
9 | | -// ReSharper disable AccessToModifiedClosure |
10 | 9 | namespace Hi3Helper.Plugin.Core.Utility; |
11 | 10 |
|
12 | 11 | /// <summary> |
@@ -48,164 +47,174 @@ public static nint AsResult<T>(this Task<T> task) |
48 | 47 | /// <param name="resultP">A pointer of the <see cref="ComAsyncResult"/> struct.</param> |
49 | 48 | /// <returns>An awaitable managed <see cref="Task"/>.</returns> |
50 | 49 | public static Task AsTask(this nint resultP) |
51 | | - { |
52 | | - nint waitHandleP = ComAsyncResult.GetWaitHandle(resultP); |
53 | | - |
54 | | - RegisteredWaitHandle? registeredWaitHandle = null; |
55 | | - TaskCompletionSource tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); |
56 | | - SafeWaitHandle safeHandle = new SafeWaitHandle(waitHandleP, false); |
57 | | - |
58 | | - WaitHandle waitHandle = new EventWaitHandle(false, EventResetMode.ManualReset) |
59 | | - { |
60 | | - SafeWaitHandle = safeHandle |
61 | | - }; |
62 | | - |
63 | | - registeredWaitHandle = ThreadPool.RegisterWaitForSingleObject(waitHandle, Impl, null, -1, true); |
64 | | - |
65 | | - return tcs.Task; |
66 | | - |
67 | | - unsafe void Impl(object? state, bool isTimedOut) |
68 | | - { |
69 | | - SetResult<TaskCompletionSource, nint>(resultP.AsPointer<ComAsyncResult>(), tcs); |
70 | | - |
71 | | - safeHandle.Dispose(); |
72 | | - waitHandle.Dispose(); |
73 | | - ComAsyncResult.FreeResult(resultP); |
74 | | - |
75 | | - if (waitHandleP != nint.Zero) |
76 | | - { |
77 | | - PInvoke.CloseHandle(waitHandleP); |
78 | | - } |
79 | | - |
80 | | - registeredWaitHandle!.Unregister(null); |
81 | | - } |
82 | | - } |
| 50 | + => new RegisteredWaitHandleContext(resultP).Task; |
83 | 51 |
|
84 | 52 | /// <summary> |
85 | 53 | /// Re-marshal a pointer of <see cref="ComAsyncResult"/> struct into a managed <see cref="Task"/>. |
86 | 54 | /// </summary> |
87 | 55 | /// <param name="resultP">A pointer of the <see cref="ComAsyncResult"/> struct.</param> |
88 | 56 | /// <returns>An awaitable managed <see cref="Task"/>.</returns> |
89 | 57 | public static Task<T> AsTask<T>(this nint resultP) |
90 | | - where T : unmanaged |
| 58 | + where T : unmanaged => (Task<T>)new RegisteredWaitHandleContext<T>(resultP).Task; |
| 59 | + |
| 60 | + /// <summary> |
| 61 | + /// A wait handle context to registers and returns a result once async operation is completed. |
| 62 | + /// </summary> |
| 63 | + private class RegisteredWaitHandleContext |
91 | 64 | { |
92 | | - nint waitHandleP = ComAsyncResult.GetWaitHandle(resultP); |
| 65 | + protected nint AsyncResultP { get; } |
| 66 | + private nint UnmanagedWaitHandleP { get; } |
| 67 | + private SafeWaitHandle SafeWaitHandle { get; } |
| 68 | + private WaitHandle WaitHandle { get; } |
| 69 | + private RegisteredWaitHandle RegisteredWaitHandle { get; } |
93 | 70 |
|
94 | | - RegisteredWaitHandle? registeredWaitHandle = null; |
95 | | - TaskCompletionSource<T> taskSource = new(TaskCreationOptions.RunContinuationsAsynchronously); |
96 | | - SafeWaitHandle safeHandle = new SafeWaitHandle(waitHandleP, false); |
| 71 | + private TaskCompletionSource TaskSourceResult { get; } = new(TaskCreationOptions.RunContinuationsAsynchronously); |
97 | 72 |
|
98 | | - WaitHandle waitHandle = new EventWaitHandle(false, EventResetMode.ManualReset) |
| 73 | + public virtual Task Task => TaskSourceResult.Task; |
| 74 | + |
| 75 | + public RegisteredWaitHandleContext(nint asyncResultP) |
99 | 76 | { |
100 | | - SafeWaitHandle = safeHandle |
101 | | - }; |
| 77 | + AsyncResultP = asyncResultP; |
| 78 | + UnmanagedWaitHandleP = ComAsyncResult.GetWaitHandle(asyncResultP); |
102 | 79 |
|
103 | | - registeredWaitHandle = ThreadPool.RegisterWaitForSingleObject(waitHandle, Impl, null, -1, true); |
| 80 | + SafeWaitHandle = new SafeWaitHandle(UnmanagedWaitHandleP, false); |
| 81 | + WaitHandle = new EventWaitHandle(false, EventResetMode.ManualReset) |
| 82 | + { |
| 83 | + SafeWaitHandle = SafeWaitHandle |
| 84 | + }; |
| 85 | + RegisteredWaitHandle = ThreadPool.RegisterWaitForSingleObject(WaitHandle, SetResultCallback, this, -1, true); |
| 86 | + } |
104 | 87 |
|
105 | | - return taskSource.Task; |
| 88 | + private static void SetResultCallback(object? state, bool isTimedOut) |
| 89 | + { |
| 90 | + RegisteredWaitHandleContext ctx = (RegisteredWaitHandleContext)state!; |
| 91 | + ctx.SetResultCallbackCore(); |
| 92 | + } |
106 | 93 |
|
107 | | - unsafe void Impl(object? state, bool isTimedOut) |
| 94 | + protected virtual unsafe void SetResultCallbackCore() |
108 | 95 | { |
109 | | - SetResult<TaskCompletionSource<T>, T>(resultP.AsPointer<ComAsyncResult>(), taskSource); |
| 96 | + SetResult<TaskCompletionSource, nint>(AsyncResultP.AsPointer<ComAsyncResult>(), TaskSourceResult); |
| 97 | + CloseHandles(); |
| 98 | + } |
110 | 99 |
|
111 | | - safeHandle.Dispose(); |
112 | | - waitHandle.Dispose(); |
113 | | - ComAsyncResult.FreeResult(resultP); |
| 100 | + /// <summary> |
| 101 | + /// Closes wait handles and free <see cref="ComAsyncResult"/> struct. |
| 102 | + /// </summary> |
| 103 | + protected void CloseHandles() |
| 104 | + { |
| 105 | + SafeWaitHandle.Dispose(); |
| 106 | + WaitHandle.Dispose(); |
| 107 | + ComAsyncResult.FreeResult(AsyncResultP); |
114 | 108 |
|
115 | | - if (waitHandleP != nint.Zero) |
| 109 | + if (UnmanagedWaitHandleP != nint.Zero) |
116 | 110 | { |
117 | | - PInvoke.CloseHandle(waitHandleP); |
| 111 | + PInvoke.CloseHandle(UnmanagedWaitHandleP); |
118 | 112 | } |
119 | 113 |
|
120 | | - registeredWaitHandle!.Unregister(null); |
| 114 | + RegisteredWaitHandle.Unregister(null); |
121 | 115 | } |
122 | | - } |
123 | 116 |
|
124 | | - /// <summary> |
125 | | - /// Sets result of the async operation based on its return type into the <see cref="TaskCompletionSource"/>. |
126 | | - /// </summary> |
127 | | - /// <typeparam name="TSource">A type of <see cref="TaskCompletionSource"/> or <see cref="TaskCompletionSource{TResult}"/></typeparam> |
128 | | - /// <typeparam name="T">A type of the return value from <see cref="ComAsyncResult"/>.</typeparam> |
129 | | - /// <param name="asyncResult">A pointer to the <see cref="ComAsyncResult"/> struct.</param> |
130 | | - /// <param name="tcs">An instance of <see cref="TaskCompletionSource"/> or <see cref="TaskCompletionSource{TResult}"/> to set the exception or result to.</param> |
131 | | - /// <exception cref="InvalidCastException">Whether the <typeparamref name="TSource"/> is not either <see cref="TaskCompletionSource"/> or <see cref="TaskCompletionSource{TResult}"/></exception> |
132 | | - private static unsafe void SetResult<TSource, T>(ComAsyncResult* asyncResult, TSource tcs) |
133 | | - where T : unmanaged |
134 | | - { |
135 | | - // Try check the TCS kind. |
136 | | - switch (tcs) |
| 117 | + /// <summary> |
| 118 | + /// Sets result of the async operation based on its return type into the <see cref="TaskCompletionSource"/>. |
| 119 | + /// </summary> |
| 120 | + /// <typeparam name="TSource">A type of <see cref="TaskCompletionSource"/> or <see cref="TaskCompletionSource{TResult}"/></typeparam> |
| 121 | + /// <typeparam name="T">A type of the return value from <see cref="ComAsyncResult"/>.</typeparam> |
| 122 | + /// <param name="asyncResult">A pointer to the <see cref="ComAsyncResult"/> struct.</param> |
| 123 | + /// <param name="tcs">An instance of <see cref="TaskCompletionSource"/> or <see cref="TaskCompletionSource{TResult}"/> to set the exception or result to.</param> |
| 124 | + /// <exception cref="InvalidCastException">Whether the <typeparamref name="TSource"/> is not either <see cref="TaskCompletionSource"/> or <see cref="TaskCompletionSource{TResult}"/></exception> |
| 125 | + protected static unsafe void SetResult<TSource, T>(ComAsyncResult* asyncResult, TSource tcs) |
| 126 | + where T : unmanaged |
137 | 127 | { |
138 | | - case TaskCompletionSource<T> asTcsRes: |
139 | | - SetResultInner(asyncResult, () => asTcsRes.SetResult(*(T*)asyncResult->_resultP), asTcsRes.SetException, asTcsRes.SetCanceled); |
140 | | - return; |
141 | | - case TaskCompletionSource asTcs: |
142 | | - SetResultInner(asyncResult, asTcs.SetResult, asTcs.SetException, asTcs.SetCanceled); |
143 | | - return; |
144 | | - } |
| 128 | + // Try check the TCS kind. |
| 129 | + switch (tcs) |
| 130 | + { |
| 131 | + case TaskCompletionSource<T> asTcsRes: |
| 132 | + SetResultInner(asyncResult, () => asTcsRes.SetResult(*(T*)asyncResult->_resultP), asTcsRes.SetException, asTcsRes.SetCanceled); |
| 133 | + return; |
| 134 | + case TaskCompletionSource asTcs: |
| 135 | + SetResultInner(asyncResult, asTcs.SetResult, asTcs.SetException, asTcs.SetCanceled); |
| 136 | + return; |
| 137 | + } |
145 | 138 |
|
146 | | - // Bada bing, bada bong. Throw the cast exception. |
147 | | - throw new InvalidCastException($"Cannot cast {nameof(TSource)} to either {nameof(TaskCompletionSource<>)} or {nameof(TaskCompletionSource)}"); |
148 | | - } |
| 139 | + // Bada bing, bada bong. Throw the cast exception. |
| 140 | + throw new InvalidCastException($"Cannot cast {nameof(TSource)} to either {nameof(TaskCompletionSource<>)} or {nameof(TaskCompletionSource)}"); |
| 141 | + } |
149 | 142 |
|
150 | | - private static unsafe void SetResultInner(ComAsyncResult* asyncResult, Action actionSetRes, SetExceptionDelegate actionSetExc, Action actionSetCancel) |
151 | | - { |
152 | | - // IsCancelled is set without IsFaulty being set. So, try check if the exception is not empty. If yes, then set the default exception. |
153 | | - if (asyncResult->IsCancelled) |
| 143 | + private static unsafe void SetResultInner(ComAsyncResult* asyncResult, Action actionSetRes, SetExceptionDelegate actionSetExc, Action actionSetCancel) |
154 | 144 | { |
155 | | - if (asyncResult->ExceptionMemory.IsEmpty) |
| 145 | + // IsCancelled is set without IsFaulty being set. So, try check if the exception is not empty. If yes, then set the default exception. |
| 146 | + if (asyncResult->IsCancelled) |
| 147 | + { |
| 148 | + if (asyncResult->ExceptionMemory.IsEmpty) |
| 149 | + { |
| 150 | + actionSetExc(new TaskCanceledException()); |
| 151 | + return; |
| 152 | + } |
| 153 | + |
| 154 | + // Otherwise, set the exception from ExceptionMemory |
| 155 | + SetException(asyncResult, actionSetExc, actionSetCancel); |
| 156 | + return; |
| 157 | + } |
| 158 | + |
| 159 | + // If it isn't faulty, invoke result. |
| 160 | + if (!asyncResult->IsFaulty) |
156 | 161 | { |
157 | | - actionSetExc(new TaskCanceledException()); |
| 162 | + actionSetRes(); |
158 | 163 | return; |
159 | 164 | } |
160 | 165 |
|
161 | | - // Otherwise, set the exception from ExceptionMemory |
| 166 | + // Otherwise, invoke the exception. |
162 | 167 | SetException(asyncResult, actionSetExc, actionSetCancel); |
163 | | - return; |
164 | 168 | } |
165 | 169 |
|
166 | | - // If it isn't faulty, invoke result. |
167 | | - if (!asyncResult->IsFaulty) |
| 170 | + /// <summary> |
| 171 | + /// Set the exception provided by the <see cref="ComAsyncResult.ExceptionMemory"/>. |
| 172 | + /// </summary> |
| 173 | + /// <param name="asyncResult">A pointer to the <see cref="ComAsyncResult"/> struct.</param> |
| 174 | + /// <param name="actionSetExc">A callback to set the exception into the <see cref="TaskCompletionSource"/></param> |
| 175 | + /// <param name="actionSetCancel">A callback to set the canceled status into the <see cref="TaskCompletionSource"/></param> |
| 176 | + /// <remarks> |
| 177 | + /// If the buffer provided by the <see cref="ComAsyncResult.ExceptionMemory"/> is empty, then a generic <see cref="COMException"/> will be set. |
| 178 | + /// </remarks> |
| 179 | + private static unsafe void SetException(ComAsyncResult* asyncResult, SetExceptionDelegate actionSetExc, Action actionSetCancel) |
168 | 180 | { |
169 | | - actionSetRes(); |
170 | | - return; |
171 | | - } |
| 181 | + StackTrace currentStackTrace = new(true); |
| 182 | + Exception? exception = ComAsyncException.GetExceptionFromHandle(asyncResult->ExceptionMemory); |
172 | 183 |
|
173 | | - // Otherwise, invoke the exception. |
174 | | - SetException(asyncResult, actionSetExc, actionSetCancel); |
175 | | - } |
| 184 | +#if DEBUG |
| 185 | + Debug.Assert(exception != null, $"Exception shouldn't be null! Result address is: 0x{(nint)asyncResult:x8}"); |
| 186 | +#endif |
176 | 187 |
|
177 | | - /// <summary> |
178 | | - /// Set the exception provided by the <see cref="ComAsyncResult.ExceptionMemory"/>. |
179 | | - /// </summary> |
180 | | - /// <param name="asyncResult">A pointer to the <see cref="ComAsyncResult"/> struct.</param> |
181 | | - /// <param name="actionSetExc">A callback to set the exception into the <see cref="TaskCompletionSource"/></param> |
182 | | - /// <param name="actionSetCancel">A callback to set the canceled status into the <see cref="TaskCompletionSource"/></param> |
183 | | - /// <remarks> |
184 | | - /// If the buffer provided by the <see cref="ComAsyncResult.ExceptionMemory"/> is empty, then a generic <see cref="COMException"/> will be set. |
185 | | - /// </remarks> |
186 | | - private static unsafe void SetException(ComAsyncResult* asyncResult, SetExceptionDelegate actionSetExc, Action actionSetCancel) |
187 | | - { |
188 | | - StackTrace currentStackTrace = new(true); |
189 | | - Exception? exception = ComAsyncException.GetExceptionFromHandle(asyncResult->ExceptionMemory); |
| 188 | + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract |
| 189 | + if (exception == null) |
| 190 | + { |
| 191 | + actionSetExc(new COMException()); |
| 192 | + return; |
| 193 | + } |
190 | 194 |
|
191 | | - #if DEBUG |
192 | | - Debug.Assert(exception != null, $"Exception shouldn't be null! Result address is: 0x{(nint)asyncResult:x8}"); |
193 | | - #endif |
| 195 | + exception.SetExceptionRemoteStackTrace() += Environment.NewLine; |
| 196 | + exception.SetExceptionStackTrace() = currentStackTrace.ToString(); |
| 197 | + actionSetExc(exception); |
194 | 198 |
|
195 | | - // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract |
196 | | - if (exception == null) |
197 | | - { |
198 | | - actionSetExc(new COMException()); |
199 | | - return; |
| 199 | + if (exception is OperationCanceledException) |
| 200 | + { |
| 201 | + actionSetCancel(); |
| 202 | + } |
200 | 203 | } |
| 204 | + } |
| 205 | + |
| 206 | + /// <inheritdoc/> |
| 207 | + private class RegisteredWaitHandleContext<T>(nint asyncResultP) : RegisteredWaitHandleContext(asyncResultP) |
| 208 | + where T : unmanaged |
| 209 | + { |
| 210 | + private TaskCompletionSource<T> TaskSourceResult { get; } = new(TaskCreationOptions.RunContinuationsAsynchronously); |
201 | 211 |
|
202 | | - exception.SetExceptionRemoteStackTrace() += Environment.NewLine; |
203 | | - exception.SetExceptionStackTrace() = currentStackTrace.ToString(); |
204 | | - actionSetExc(exception); |
| 212 | + public override Task Task => TaskSourceResult.Task; |
205 | 213 |
|
206 | | - if (exception is OperationCanceledException) |
| 214 | + protected override unsafe void SetResultCallbackCore() |
207 | 215 | { |
208 | | - actionSetCancel(); |
| 216 | + SetResult<TaskCompletionSource<T>, T>(AsyncResultP.AsPointer<ComAsyncResult>(), TaskSourceResult); |
| 217 | + CloseHandles(); |
209 | 218 | } |
210 | 219 | } |
211 | 220 | } |
0 commit comments