4
4
using System ;
5
5
using System . Collections . Generic ;
6
6
using System . Diagnostics ;
7
+ using System . Threading ;
7
8
using System . Threading . Tasks ;
8
9
using Microsoft . AspNetCore . Components . RenderTree ;
9
10
@@ -19,7 +20,6 @@ public abstract class Renderer : IDisposable
19
20
private readonly Dictionary < int , ComponentState > _componentStateById = new Dictionary < int , ComponentState > ( ) ;
20
21
private readonly RenderBatchBuilder _batchBuilder = new RenderBatchBuilder ( ) ;
21
22
private readonly Dictionary < int , EventHandlerInvoker > _eventBindings = new Dictionary < int , EventHandlerInvoker > ( ) ;
22
- private IDispatcher _dispatcher ;
23
23
24
24
private int _nextComponentId = 0 ; // TODO: change to 'long' when Mono .NET->JS interop supports it
25
25
private bool _isBatchInProgress ;
@@ -33,15 +33,15 @@ public event UnhandledExceptionEventHandler UnhandledSynchronizationException
33
33
{
34
34
add
35
35
{
36
- if ( ! ( _dispatcher is RendererSynchronizationContext rendererSynchronizationContext ) )
36
+ if ( ! ( Dispatcher is RendererSynchronizationContext rendererSynchronizationContext ) )
37
37
{
38
38
return ;
39
39
}
40
40
rendererSynchronizationContext . UnhandledException += value ;
41
41
}
42
42
remove
43
43
{
44
- if ( ! ( _dispatcher is RendererSynchronizationContext rendererSynchronizationContext ) )
44
+ if ( ! ( Dispatcher is RendererSynchronizationContext rendererSynchronizationContext ) )
45
45
{
46
46
return ;
47
47
}
@@ -65,9 +65,14 @@ public Renderer(IServiceProvider serviceProvider)
65
65
/// <param name="dispatcher">The <see cref="IDispatcher"/> to be for invoking user actions into the <see cref="Renderer"/> context.</param>
66
66
public Renderer ( IServiceProvider serviceProvider , IDispatcher dispatcher ) : this ( serviceProvider )
67
67
{
68
- _dispatcher = dispatcher ;
68
+ Dispatcher = dispatcher ;
69
69
}
70
70
71
+ /// <summary>
72
+ /// Gets the <see cref="IDispatcher"/>.
73
+ /// </summary>
74
+ protected IDispatcher Dispatcher { get ; }
75
+
71
76
/// <summary>
72
77
/// Creates an <see cref="IDispatcher"/> that can be used with one or more <see cref="Renderer"/>.
73
78
/// </summary>
@@ -130,6 +135,11 @@ protected Task RenderRootComponentAsync(int componentId)
130
135
/// </remarks>
131
136
protected async Task RenderRootComponentAsync ( int componentId , ParameterCollection initialParameters )
132
137
{
138
+ if ( Interlocked . CompareExchange ( ref _pendingTasks , new List < Task > ( ) , null ) != null )
139
+ {
140
+ throw new InvalidOperationException ( "There is an ongoing rendering in progress." ) ;
141
+ }
142
+
133
143
// During the rendering process we keep a list of components performing work in _pendingTasks.
134
144
// _renderer.AddToPendingTasks will be called by ComponentState.SetDirectParameters to add the
135
145
// the Task produced by Component.SetParametersAsync to _pendingTasks in order to track the
@@ -176,8 +186,8 @@ private async Task ProcessAsynchronousWork()
176
186
177
187
try
178
188
{
179
- await pendingWork ;
180
- }
189
+ await pendingWork ;
190
+ }
181
191
catch when ( ! pendingWork . IsCanceled )
182
192
{
183
193
// await will unwrap an AggregateException and throw exactly one inner exception.
@@ -251,13 +261,13 @@ public async Task DispatchEventAsync(int componentId, int eventHandlerId, UIEven
251
261
public virtual Task Invoke ( Action workItem )
252
262
{
253
263
// This is for example when we run on a system with a single thread, like WebAssembly.
254
- if ( _dispatcher == null )
264
+ if ( Dispatcher == null )
255
265
{
256
266
workItem ( ) ;
257
267
return Task . CompletedTask ;
258
268
}
259
269
260
- if ( SynchronizationContext . Current == _dispatcher )
270
+ if ( SynchronizationContext . Current == Dispatcher )
261
271
{
262
272
// This is an optimization for when the dispatcher is also a syncronization context, like in the default case.
263
273
// No need to dispatch. Avoid deadlock by invoking directly.
@@ -266,7 +276,7 @@ public virtual Task Invoke(Action workItem)
266
276
}
267
277
else
268
278
{
269
- return _dispatcher . Invoke ( workItem ) ;
279
+ return Dispatcher . Invoke ( workItem ) ;
270
280
}
271
281
}
272
282
@@ -278,21 +288,21 @@ public virtual Task Invoke(Action workItem)
278
288
public virtual Task InvokeAsync ( Func < Task > workItem )
279
289
{
280
290
// This is for example when we run on a system with a single thread, like WebAssembly.
281
- if ( _dispatcher == null )
291
+ if ( Dispatcher == null )
282
292
{
283
293
workItem ( ) ;
284
294
return Task . CompletedTask ;
285
295
}
286
296
287
- if ( SynchronizationContext . Current == _dispatcher )
297
+ if ( SynchronizationContext . Current == Dispatcher )
288
298
{
289
299
// This is an optimization for when the dispatcher is also a syncronization context, like in the default case.
290
300
// No need to dispatch. Avoid deadlock by invoking directly.
291
301
return workItem ( ) ;
292
302
}
293
303
else
294
304
{
295
- return _dispatcher . InvokeAsync ( workItem ) ;
305
+ return Dispatcher . InvokeAsync ( workItem ) ;
296
306
}
297
307
}
298
308
@@ -388,7 +398,7 @@ private void EnsureSynchronizationContext()
388
398
// Plus, any other logic that mutates state accessed during rendering also
389
399
// needs not to run concurrently with rendering so should be dispatched to
390
400
// the renderer's sync context.
391
- if ( _dispatcher is SynchronizationContext synchronizationContext && SynchronizationContext . Current != synchronizationContext )
401
+ if ( Dispatcher is SynchronizationContext synchronizationContext && SynchronizationContext . Current != synchronizationContext )
392
402
{
393
403
throw new InvalidOperationException (
394
404
"The current thread is not associated with the renderer's synchronization context. " +
@@ -442,24 +452,32 @@ private async void InvokeRenderCompletedCalls(ArrayRange<RenderTreeDiff> updated
442
452
var componentState = GetOptionalComponentState ( array [ i ] . ComponentId ) ;
443
453
if ( componentState != null )
444
454
{
445
- // The component might be rendered and disposed in the same batch (if its parent
446
- // was rendered later in the batch, and removed the child from the tree).
455
+ // The component might be rendered and disposed in the same batch (if its parent
456
+ // was rendered later in the batch, and removed the child from the tree).
447
457
var task = componentState . NotifyRenderCompletedAsync ( ) ;
448
458
449
459
// We want to avoid allocations per rendering. Avoid allocating a state machine or an accumulator
450
460
// unless we absolutely have to.
451
- if ( task . IsCompleted || task . IsCanceled )
461
+ if ( task . IsCompleted )
452
462
{
453
- // Nothing to do here.
454
- continue ;
463
+ if ( task . Status == TaskStatus . RanToCompletion || task . Status == TaskStatus . Canceled )
464
+ {
465
+ // Nothing to do here.
466
+ continue ;
467
+ }
468
+ else if ( task . Status == TaskStatus . Faulted )
469
+ {
470
+ HandleException ( task . Exception ) ;
471
+ continue ;
472
+ }
455
473
}
456
474
457
475
// Either the Task is incomplete or is faulted.
458
476
// In either case, queue up the task and we can inspect it later.
459
477
batch = batch ?? new List < Task > ( ) ;
460
478
batch . Add ( task ) ;
479
+ }
461
480
}
462
- }
463
481
464
482
if ( batch != null )
465
483
{
@@ -538,7 +556,7 @@ protected virtual void Dispose(bool disposing)
538
556
}
539
557
}
540
558
}
541
- }
559
+ }
542
560
543
561
/// <summary>
544
562
/// Releases all resources currently used by this <see cref="Renderer"/> instance.
0 commit comments