@@ -32,7 +32,7 @@ public abstract class ComponentBase : IComponent, IHandleEvent, IHandleAfterRend
32
32
33
33
private readonly RenderFragment _renderFragment ;
34
34
private RenderHandle _renderHandle ;
35
- private bool _hasCalledInit ;
35
+ private bool _initialized ;
36
36
private bool _hasNeverRendered = true ;
37
37
private bool _hasPendingQueuedRender ;
38
38
@@ -177,188 +177,93 @@ void IComponent.Configure(RenderHandle renderHandle)
177
177
public virtual Task SetParametersAsync ( ParameterCollection parameters )
178
178
{
179
179
parameters . SetParameterProperties ( this ) ;
180
- if ( ! _hasCalledInit )
180
+ if ( ! _initialized )
181
181
{
182
- return RunInitAndSetParameters ( ) ;
182
+ _initialized = true ;
183
+
184
+ return RunInitAndSetParametersAsync ( ) ;
183
185
}
184
186
else
185
187
{
186
- OnParametersSet ( ) ;
187
- // If you override OnInitAsync or OnParametersSetAsync and return a noncompleted task,
188
- // then by default we automatically re-render once each of those tasks completes.
189
- var isAsync = false ;
190
- Task parametersTask = null ;
191
- ( isAsync , parametersTask ) = ProcessLifeCycletask ( OnParametersSetAsync ( ) ) ;
192
- StateHasChanged ( ) ;
193
- // We call StateHasChanged here so that we render after OnParametersSet and after the
194
- // synchronous part of OnParametersSetAsync has run, and in case there is async work
195
- // we trigger another render.
196
- if ( isAsync )
197
- {
198
- return parametersTask ;
199
- }
200
-
201
- return Task . CompletedTask ;
188
+ return SetParametersAsyncCore ( ) ;
202
189
}
203
190
}
204
191
205
- private async Task RunInitAndSetParameters ( )
192
+ private async Task RunInitAndSetParametersAsync ( )
206
193
{
207
- _hasCalledInit = true ;
208
- var initIsAsync = false ;
209
-
210
194
OnInit ( ) ;
211
- Task initTask = null ;
212
- ( initIsAsync , initTask ) = ProcessLifeCycletask ( OnInitAsync ( ) ) ;
213
- if ( initIsAsync )
195
+ var task = OnInitAsync ( ) ;
196
+
197
+ if ( task . Status != TaskStatus . RanToCompletion && task . Status != TaskStatus . Canceled )
214
198
{
215
199
// Call state has changed here so that we render after the sync part of OnInitAsync has run
216
200
// and wait for it to finish before we continue. If no async work has been done yet, we want
217
201
// to defer calling StateHasChanged up until the first bit of async code happens or until
218
- // the end.
202
+ // the end. Additionally, we want to avoid calling StateHasChanged if no
203
+ // async work is to be performed.
219
204
StateHasChanged ( ) ;
220
- await initTask ;
221
- }
222
205
223
- OnParametersSet ( ) ;
224
- Task parametersTask = null ;
225
- var setParametersIsAsync = false ;
226
- ( setParametersIsAsync , parametersTask ) = ProcessLifeCycletask ( OnParametersSetAsync ( ) ) ;
227
- // We always call StateHasChanged here as we want to trigger a rerender after OnParametersSet and
228
- // the synchronous part of OnParametersSetAsync has run, triggering another re-render in case there
229
- // is additional async work.
230
- StateHasChanged ( ) ;
231
- if ( setParametersIsAsync )
232
- {
233
- await parametersTask ;
206
+ await ProcessLifecycleTaskAsync ( task ) ;
234
207
}
208
+
209
+ await SetParametersAsyncCore ( ) ;
235
210
}
236
211
237
- private ( bool isAsync , Task asyncTask ) ProcessLifeCycletask ( Task task )
212
+ private Task SetParametersAsyncCore ( )
238
213
{
239
- if ( task == null )
240
- {
241
- throw new ArgumentNullException ( nameof ( task ) ) ;
242
- }
214
+ OnParametersSet ( ) ;
215
+ var task = OnParametersSetAsync ( ) ;
216
+ // If no aync work is to be performed, i.e. the task has already ran to completion
217
+ // or was canceled by the time we got to inspect it, avoid going async and re-invoking
218
+ // StateHasChanged at the culmination of the async work.
219
+ var shouldAwaitTask = task . Status != TaskStatus . RanToCompletion &&
220
+ task . Status != TaskStatus . Canceled ;
243
221
244
- switch ( task . Status )
245
- {
246
- // If it's already completed synchronously, no need to await and no
247
- // need to issue a further render (we already rerender synchronously).
248
- // Just need to make sure we propagate any errors.
249
- case TaskStatus . RanToCompletion :
250
- case TaskStatus . Canceled :
251
- return ( false , null ) ;
252
- case TaskStatus . Faulted :
253
- HandleException ( task . Exception ) ;
254
- return ( false , null ) ;
255
- // For incomplete tasks, automatically re-render on successful completion
256
- default :
257
- return ( true , ReRenderAsyncTask ( task ) ) ;
258
- }
222
+ // We always call StateHasChanged here as we want to trigger a rerender after OnParametersSet and
223
+ // the synchronous part of OnParametersSetAsync has run.
224
+ StateHasChanged ( ) ;
225
+
226
+ return shouldAwaitTask ?
227
+ ProcessLifecycleTaskAsync ( task ) :
228
+ Task . CompletedTask ;
259
229
}
260
230
261
- private async Task ReRenderAsyncTask ( Task task )
231
+ private async Task ProcessLifecycleTaskAsync ( Task task )
262
232
{
263
233
try
264
234
{
265
235
await task ;
266
- StateHasChanged ( ) ;
267
236
}
268
- catch ( Exception ex )
237
+ catch when ( task . IsCanceled )
269
238
{
270
- // Either the task failed, or it was cancelled, or StateHasChanged threw.
271
- // We want to report task failure or StateHasChanged exceptions only.
272
- if ( ! task . IsCanceled )
273
- {
274
- HandleException ( ex ) ;
275
- }
276
- }
277
- }
278
-
279
- private async void ContinueAfterLifecycleTask ( Task task )
280
- {
281
- switch ( task == null ? TaskStatus . RanToCompletion : task . Status )
282
- {
283
- // If it's already completed synchronously, no need to await and no
284
- // need to issue a further render (we already rerender synchronously).
285
- // Just need to make sure we propagate any errors.
286
- case TaskStatus . RanToCompletion :
287
- case TaskStatus . Canceled :
288
- break ;
289
- case TaskStatus . Faulted :
290
- HandleException ( task . Exception ) ;
291
- break ;
292
-
293
- // For incomplete tasks, automatically re-render on successful completion
294
- default :
295
- try
296
- {
297
- await task ;
298
- StateHasChanged ( ) ;
299
- }
300
- catch ( Exception ex )
301
- {
302
- // Either the task failed, or it was cancelled, or StateHasChanged threw.
303
- // We want to report task failure or StateHasChanged exceptions only.
304
- if ( ! task . IsCanceled )
305
- {
306
- HandleException ( ex ) ;
307
- }
308
- }
309
-
310
- break ;
311
- }
312
- }
313
-
314
- private static void HandleException ( Exception ex )
315
- {
316
- if ( ex is AggregateException && ex . InnerException != null )
317
- {
318
- ex = ex . InnerException ; // It's more useful
239
+ // Ignore task cancelation but don't bother issuing a state change.
240
+ return ;
319
241
}
320
242
321
- // TODO: Need better global exception handling
322
- Console . Error . WriteLine ( $ "[{ ex . GetType ( ) . FullName } ] { ex . Message } \n { ex . StackTrace } ") ;
243
+ StateHasChanged ( ) ;
323
244
}
324
245
325
- void IHandleEvent . HandleEvent ( EventHandlerInvoker binding , UIEventArgs args )
246
+ async Task IHandleEvent . HandleEventAsync ( EventHandlerInvoker binding , UIEventArgs args )
326
247
{
327
- var task = binding . Invoke ( args ) ;
328
- ContinueAfterLifecycleTask ( task ) ;
248
+ await binding . Invoke ( args ) ;
329
249
330
250
// After each event, we synchronously re-render (unless !ShouldRender())
331
251
// This just saves the developer the trouble of putting "StateHasChanged();"
332
252
// at the end of every event callback.
333
253
StateHasChanged ( ) ;
334
254
}
335
255
336
- void IHandleAfterRender . OnAfterRender ( )
256
+ Task IHandleAfterRender . OnAfterRenderAsync ( )
337
257
{
338
258
OnAfterRender ( ) ;
339
259
340
- var onAfterRenderTask = OnAfterRenderAsync ( ) ;
341
- if ( onAfterRenderTask != null && onAfterRenderTask . Status != TaskStatus . RanToCompletion )
342
- {
343
- // Note that we don't call StateHasChanged to trigger a render after
344
- // handling this, because that would be an infinite loop. The only
345
- // reason we have OnAfterRenderAsync is so that the developer doesn't
346
- // have to use "async void" and do their own exception handling in
347
- // the case where they want to start an async task.
348
- var taskWithHandledException = HandleAfterRenderException ( onAfterRenderTask ) ;
349
- }
350
- }
260
+ return OnAfterRenderAsync ( ) ;
351
261
352
- private async Task HandleAfterRenderException ( Task parentTask )
353
- {
354
- try
355
- {
356
- await parentTask ;
357
- }
358
- catch ( Exception e )
359
- {
360
- HandleException ( e ) ;
361
- }
262
+ // Note that we don't call StateHasChanged to trigger a render after
263
+ // handling this, because that would be an infinite loop. The only
264
+ // reason we have OnAfterRenderAsync is so that the developer doesn't
265
+ // have to use "async void" and do their own exception handling in
266
+ // the case where they want to start an async task.
362
267
}
363
268
}
364
269
}
0 commit comments