Skip to content

Commit 3768419

Browse files
committed
Changes per PR comments
1 parent 547a49d commit 3768419

File tree

12 files changed

+246
-99
lines changed

12 files changed

+246
-99
lines changed

src/Components/Blazor/Blazor/src/Rendering/WebAssemblyRenderer.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public void AddComponent<TComponent>(string domElementSelector)
4949
/// </summary>
5050
/// <param name="componentType">The type of the component.</param>
5151
/// <param name="domElementSelector">A CSS selector that uniquely identifies a DOM element.</param>
52-
public void AddComponent(Type componentType, string domElementSelector)
52+
public async void AddComponent(Type componentType, string domElementSelector)
5353
{
5454
var component = InstantiateComponent(componentType);
5555
var componentId = AssignRootComponentId(component);
@@ -65,7 +65,8 @@ public void AddComponent(Type componentType, string domElementSelector)
6565
domElementSelector,
6666
componentId);
6767

68-
RenderRootComponent(componentId);
68+
// Dispatch the rendering. In the most common cases, this will finish synchronously
69+
await RenderRootComponentAsync(componentId);
6970
}
7071

7172
/// <inheritdoc />
@@ -94,7 +95,7 @@ protected override Task UpdateDisplayAsync(in RenderBatch batch)
9495
}
9596

9697
/// <inheritdoc />
97-
protected override bool HandleException(Exception exception)
98+
protected override void HandleException(Exception exception)
9899
{
99100
Console.Error.WriteLine($"Unhandled exception rendering component:");
100101
if (exception is AggregateException aggregateException)
@@ -108,8 +109,6 @@ protected override bool HandleException(Exception exception)
108109
{
109110
Console.Error.WriteLine(exception);
110111
}
111-
112-
return true;
113112
}
114113
}
115114
}

src/Components/Blazor/Build/test/RazorIntegrationTestBase.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,11 @@ public TestRenderer() : base(new TestServiceProvider(), CreateDefaultDispatcher(
443443
public void AttachComponent(IComponent component)
444444
=> AssignRootComponentId(component);
445445

446+
protected override void HandleException(Exception exception)
447+
{
448+
throw new NotImplementedException();
449+
}
450+
446451
protected override Task UpdateDisplayAsync(in RenderBatch renderBatch)
447452
{
448453
LatestBatchReferenceFrames = renderBatch.ReferenceFrames.ToArray();

src/Components/Browser.JS/src/package-lock.json

Lines changed: 7 additions & 18 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Components/Components/perf/RenderTreeDiffBuilderBenchmark.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,11 @@ public FakeRenderer()
9191
{
9292
}
9393

94+
protected override void HandleException(Exception exception)
95+
{
96+
throw new NotImplementedException();
97+
}
98+
9499
protected override Task UpdateDisplayAsync(in RenderBatch renderBatch)
95100
=> Task.CompletedTask;
96101
}

src/Components/Components/src/Rendering/Renderer.cs

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System;
55
using System.Collections.Generic;
66
using System.Diagnostics;
7+
using System.Threading;
78
using System.Threading.Tasks;
89
using Microsoft.AspNetCore.Components.RenderTree;
910

@@ -19,7 +20,6 @@ public abstract class Renderer : IDisposable
1920
private readonly Dictionary<int, ComponentState> _componentStateById = new Dictionary<int, ComponentState>();
2021
private readonly RenderBatchBuilder _batchBuilder = new RenderBatchBuilder();
2122
private readonly Dictionary<int, EventHandlerInvoker> _eventBindings = new Dictionary<int, EventHandlerInvoker>();
22-
private IDispatcher _dispatcher;
2323

2424
private int _nextComponentId = 0; // TODO: change to 'long' when Mono .NET->JS interop supports it
2525
private bool _isBatchInProgress;
@@ -33,15 +33,15 @@ public event UnhandledExceptionEventHandler UnhandledSynchronizationException
3333
{
3434
add
3535
{
36-
if (!(_dispatcher is RendererSynchronizationContext rendererSynchronizationContext))
36+
if (!(Dispatcher is RendererSynchronizationContext rendererSynchronizationContext))
3737
{
3838
return;
3939
}
4040
rendererSynchronizationContext.UnhandledException += value;
4141
}
4242
remove
4343
{
44-
if (!(_dispatcher is RendererSynchronizationContext rendererSynchronizationContext))
44+
if (!(Dispatcher is RendererSynchronizationContext rendererSynchronizationContext))
4545
{
4646
return;
4747
}
@@ -65,9 +65,14 @@ public Renderer(IServiceProvider serviceProvider)
6565
/// <param name="dispatcher">The <see cref="IDispatcher"/> to be for invoking user actions into the <see cref="Renderer"/> context.</param>
6666
public Renderer(IServiceProvider serviceProvider, IDispatcher dispatcher) : this(serviceProvider)
6767
{
68-
_dispatcher = dispatcher;
68+
Dispatcher = dispatcher;
6969
}
7070

71+
/// <summary>
72+
/// Gets the <see cref="IDispatcher"/>.
73+
/// </summary>
74+
protected IDispatcher Dispatcher { get; }
75+
7176
/// <summary>
7277
/// Creates an <see cref="IDispatcher"/> that can be used with one or more <see cref="Renderer"/>.
7378
/// </summary>
@@ -130,6 +135,11 @@ protected Task RenderRootComponentAsync(int componentId)
130135
/// </remarks>
131136
protected async Task RenderRootComponentAsync(int componentId, ParameterCollection initialParameters)
132137
{
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+
133143
// During the rendering process we keep a list of components performing work in _pendingTasks.
134144
// _renderer.AddToPendingTasks will be called by ComponentState.SetDirectParameters to add the
135145
// the Task produced by Component.SetParametersAsync to _pendingTasks in order to track the
@@ -176,8 +186,8 @@ private async Task ProcessAsynchronousWork()
176186

177187
try
178188
{
179-
await pendingWork;
180-
}
189+
await pendingWork;
190+
}
181191
catch when (!pendingWork.IsCanceled)
182192
{
183193
// await will unwrap an AggregateException and throw exactly one inner exception.
@@ -251,13 +261,13 @@ public async Task DispatchEventAsync(int componentId, int eventHandlerId, UIEven
251261
public virtual Task Invoke(Action workItem)
252262
{
253263
// This is for example when we run on a system with a single thread, like WebAssembly.
254-
if (_dispatcher == null)
264+
if (Dispatcher == null)
255265
{
256266
workItem();
257267
return Task.CompletedTask;
258268
}
259269

260-
if (SynchronizationContext.Current == _dispatcher)
270+
if (SynchronizationContext.Current == Dispatcher)
261271
{
262272
// This is an optimization for when the dispatcher is also a syncronization context, like in the default case.
263273
// No need to dispatch. Avoid deadlock by invoking directly.
@@ -266,7 +276,7 @@ public virtual Task Invoke(Action workItem)
266276
}
267277
else
268278
{
269-
return _dispatcher.Invoke(workItem);
279+
return Dispatcher.Invoke(workItem);
270280
}
271281
}
272282

@@ -278,21 +288,21 @@ public virtual Task Invoke(Action workItem)
278288
public virtual Task InvokeAsync(Func<Task> workItem)
279289
{
280290
// This is for example when we run on a system with a single thread, like WebAssembly.
281-
if (_dispatcher == null)
291+
if (Dispatcher == null)
282292
{
283293
workItem();
284294
return Task.CompletedTask;
285295
}
286296

287-
if (SynchronizationContext.Current == _dispatcher)
297+
if (SynchronizationContext.Current == Dispatcher)
288298
{
289299
// This is an optimization for when the dispatcher is also a syncronization context, like in the default case.
290300
// No need to dispatch. Avoid deadlock by invoking directly.
291301
return workItem();
292302
}
293303
else
294304
{
295-
return _dispatcher.InvokeAsync(workItem);
305+
return Dispatcher.InvokeAsync(workItem);
296306
}
297307
}
298308

@@ -388,7 +398,7 @@ private void EnsureSynchronizationContext()
388398
// Plus, any other logic that mutates state accessed during rendering also
389399
// needs not to run concurrently with rendering so should be dispatched to
390400
// the renderer's sync context.
391-
if (_dispatcher is SynchronizationContext synchronizationContext && SynchronizationContext.Current != synchronizationContext)
401+
if (Dispatcher is SynchronizationContext synchronizationContext && SynchronizationContext.Current != synchronizationContext)
392402
{
393403
throw new InvalidOperationException(
394404
"The current thread is not associated with the renderer's synchronization context. " +
@@ -442,24 +452,32 @@ private async void InvokeRenderCompletedCalls(ArrayRange<RenderTreeDiff> updated
442452
var componentState = GetOptionalComponentState(array[i].ComponentId);
443453
if (componentState != null)
444454
{
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).
447457
var task = componentState.NotifyRenderCompletedAsync();
448458

449459
// We want to avoid allocations per rendering. Avoid allocating a state machine or an accumulator
450460
// unless we absolutely have to.
451-
if (task.IsCompleted || task.IsCanceled)
461+
if (task.IsCompleted)
452462
{
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+
}
455473
}
456474

457475
// Either the Task is incomplete or is faulted.
458476
// In either case, queue up the task and we can inspect it later.
459477
batch = batch ?? new List<Task>();
460478
batch.Add(task);
479+
}
461480
}
462-
}
463481

464482
if (batch != null)
465483
{
@@ -538,7 +556,7 @@ protected virtual void Dispose(bool disposing)
538556
}
539557
}
540558
}
541-
}
559+
}
542560

543561
/// <summary>
544562
/// Releases all resources currently used by this <see cref="Renderer"/> instance.

0 commit comments

Comments
 (0)