Blazor API Review: Design concept for Dispatcher (#11930)

* Design concept for Dispatcher

Part of: #11610

This change brings forward the Dispatcher as a more primary and more
expandable concept.
- Dispatcher shows up in more places
- Dispatcher is an abstract class for horizontal scalability
- Dispatcher has parallels with S.Windows.Threading.Dispatcher where
possible

Looking for feedback on this approach. I feel pretty strongly that
making this an abstract class is the right choice, I want to see
opinions on how much to push it into people's faces.

* WIP

* PR feedback
This commit is contained in:
Ryan Nowak 2019-07-11 17:31:57 -07:00 committed by GitHub
parent 62f00a77e6
commit a27b9fc335
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 333 additions and 249 deletions

View File

@ -59,6 +59,7 @@ namespace Microsoft.AspNetCore.Blazor.Rendering
public partial class WebAssemblyRenderer : Microsoft.AspNetCore.Components.Rendering.Renderer public partial class WebAssemblyRenderer : Microsoft.AspNetCore.Components.Rendering.Renderer
{ {
public WebAssemblyRenderer(System.IServiceProvider serviceProvider, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) : base (default(System.IServiceProvider), default(Microsoft.Extensions.Logging.ILoggerFactory)) { } public WebAssemblyRenderer(System.IServiceProvider serviceProvider, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) : base (default(System.IServiceProvider), default(Microsoft.Extensions.Logging.ILoggerFactory)) { }
public override Microsoft.AspNetCore.Components.Dispatcher Dispatcher { get { throw null; } }
public System.Threading.Tasks.Task AddComponentAsync(System.Type componentType, string domElementSelector) { throw null; } public System.Threading.Tasks.Task AddComponentAsync(System.Type componentType, string domElementSelector) { throw null; }
public System.Threading.Tasks.Task AddComponentAsync<TComponent>(string domElementSelector) where TComponent : Microsoft.AspNetCore.Components.IComponent { throw null; } public System.Threading.Tasks.Task AddComponentAsync<TComponent>(string domElementSelector) where TComponent : Microsoft.AspNetCore.Components.IComponent { throw null; }
public override System.Threading.Tasks.Task DispatchEventAsync(int eventHandlerId, Microsoft.AspNetCore.Components.Rendering.EventFieldInfo eventFieldInfo, Microsoft.AspNetCore.Components.UIEventArgs eventArgs) { throw null; } public override System.Threading.Tasks.Task DispatchEventAsync(int eventHandlerId, Microsoft.AspNetCore.Components.Rendering.EventFieldInfo eventFieldInfo, Microsoft.AspNetCore.Components.UIEventArgs eventArgs) { throw null; }

View File

@ -0,0 +1,61 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
namespace Microsoft.AspNetCore.Blazor.Rendering
{
internal class NullDispatcher : Dispatcher
{
public static readonly Dispatcher Instance = new NullDispatcher();
private NullDispatcher()
{
}
public override bool CheckAccess() => true;
public override Task InvokeAsync(Action workItem)
{
if (workItem is null)
{
throw new ArgumentNullException(nameof(workItem));
}
workItem();
return Task.CompletedTask;
}
public override Task InvokeAsync(Func<Task> workItem)
{
if (workItem is null)
{
throw new ArgumentNullException(nameof(workItem));
}
return workItem();
}
public override Task<TResult> InvokeAsync<TResult>(Func<TResult> workItem)
{
if (workItem is null)
{
throw new ArgumentNullException(nameof(workItem));
}
return Task.FromResult(workItem());
}
public override Task<TResult> InvokeAsync<TResult>(Func<Task<TResult>> workItem)
{
if (workItem is null)
{
throw new ArgumentNullException(nameof(workItem));
}
return workItem();
}
}
}

View File

@ -37,6 +37,8 @@ namespace Microsoft.AspNetCore.Blazor.Rendering
_webAssemblyRendererId = RendererRegistry.Current.Add(this); _webAssemblyRendererId = RendererRegistry.Current.Add(this);
} }
public override Dispatcher Dispatcher => NullDispatcher.Instance;
/// <summary> /// <summary>
/// Attaches a new root component to the renderer, /// Attaches a new root component to the renderer,
/// causing it to be displayed in the specified DOM element. /// causing it to be displayed in the specified DOM element.

View File

@ -385,7 +385,7 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
protected private RenderTreeFrame[] GetRenderTree(TestRenderer renderer, IComponent component) protected private RenderTreeFrame[] GetRenderTree(TestRenderer renderer, IComponent component)
{ {
renderer.AttachComponent(component); renderer.AttachComponent(component);
var task = renderer.InvokeAsync(() => component.SetParametersAsync(ParameterCollection.Empty)); var task = renderer.Dispatcher.InvokeAsync(() => component.SetParametersAsync(ParameterCollection.Empty));
// we will have to change this method if we add a test that does actual async work. // we will have to change this method if we add a test that does actual async work.
Assert.True(task.Status.HasFlag(TaskStatus.RanToCompletion) || task.Status.HasFlag(TaskStatus.Faulted)); Assert.True(task.Status.HasFlag(TaskStatus.RanToCompletion) || task.Status.HasFlag(TaskStatus.Faulted));
if (task.IsFaulted) if (task.IsFaulted)
@ -442,10 +442,12 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
protected class TestRenderer : Renderer protected class TestRenderer : Renderer
{ {
public TestRenderer() : base(new TestServiceProvider(), NullLoggerFactory.Instance, CreateDefaultDispatcher()) public TestRenderer() : base(new TestServiceProvider(), NullLoggerFactory.Instance)
{ {
} }
public override Dispatcher Dispatcher { get; } = Dispatcher.CreateDefault();
public RenderTreeFrame[] LatestBatchReferenceFrames { get; private set; } public RenderTreeFrame[] LatestBatchReferenceFrames { get; private set; }
public void AttachComponent(IComponent component) public void AttachComponent(IComponent component)

View File

@ -332,7 +332,7 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
// Trigger the change event to show it updates the property // Trigger the change event to show it updates the property
// //
// This should always complete synchronously. // This should always complete synchronously.
var task = renderer.InvokeAsync(() => setter.InvokeAsync(new UIChangeEventArgs { Value = "Modified value", })); var task = renderer.Dispatcher.InvokeAsync(() => setter.InvokeAsync(new UIChangeEventArgs { Value = "Modified value", }));
Assert.Equal(TaskStatus.RanToCompletion, task.Status); Assert.Equal(TaskStatus.RanToCompletion, task.Status);
await task; await task;
@ -367,7 +367,7 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
// Trigger the change event to show it updates the property // Trigger the change event to show it updates the property
// //
// This should always complete synchronously. // This should always complete synchronously.
var task = renderer.InvokeAsync(() => setter.InvokeAsync(new UIChangeEventArgs { Value = "Modified value", })); var task = renderer.Dispatcher.InvokeAsync(() => setter.InvokeAsync(new UIChangeEventArgs { Value = "Modified value", }));
Assert.Equal(TaskStatus.RanToCompletion, task.Status); Assert.Equal(TaskStatus.RanToCompletion, task.Status);
await task; await task;
@ -404,7 +404,7 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
// //
// This should always complete synchronously. // This should always complete synchronously.
var newDateValue = new DateTime(2018, 3, 5, 4, 5, 6); var newDateValue = new DateTime(2018, 3, 5, 4, 5, 6);
var task = renderer.InvokeAsync(() => setter.InvokeAsync(new UIChangeEventArgs { Value = newDateValue.ToString(), })); var task = renderer.Dispatcher.InvokeAsync(() => setter.InvokeAsync(new UIChangeEventArgs { Value = newDateValue.ToString(), }));
Assert.Equal(TaskStatus.RanToCompletion, task.Status); Assert.Equal(TaskStatus.RanToCompletion, task.Status);
await task; await task;
@ -440,7 +440,7 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
// Trigger the change event to show it updates the property // Trigger the change event to show it updates the property
// //
// This should always complete synchronously. // This should always complete synchronously.
var task = renderer.InvokeAsync(() => setter.InvokeAsync(new UIChangeEventArgs { Value = new DateTime(2018, 3, 5).ToString(testDateFormat), })); var task = renderer.Dispatcher.InvokeAsync(() => setter.InvokeAsync(new UIChangeEventArgs { Value = new DateTime(2018, 3, 5).ToString(testDateFormat), }));
Assert.Equal(TaskStatus.RanToCompletion, task.Status); Assert.Equal(TaskStatus.RanToCompletion, task.Status);
await task; await task;
@ -559,7 +559,7 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
// Trigger the change event to show it updates the property // Trigger the change event to show it updates the property
// //
// This should always complete synchronously. // This should always complete synchronously.
var task = renderer.InvokeAsync(() => setter.InvokeAsync(new UIChangeEventArgs() { Value = false, })); var task = renderer.Dispatcher.InvokeAsync(() => setter.InvokeAsync(new UIChangeEventArgs() { Value = false, }));
Assert.Equal(TaskStatus.RanToCompletion, task.Status); Assert.Equal(TaskStatus.RanToCompletion, task.Status);
await task; await task;
@ -595,7 +595,7 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
// Trigger the change event to show it updates the property // Trigger the change event to show it updates the property
// //
// This should always complete synchronously. // This should always complete synchronously.
var task = renderer.InvokeAsync(() => setter.InvokeAsync(new UIChangeEventArgs { Value = MyEnum.SecondValue.ToString(), })); var task = renderer.Dispatcher.InvokeAsync(() => setter.InvokeAsync(new UIChangeEventArgs { Value = MyEnum.SecondValue.ToString(), }));
Assert.Equal(TaskStatus.RanToCompletion, task.Status); Assert.Equal(TaskStatus.RanToCompletion, task.Status);
await task; await task;

View File

@ -88,10 +88,12 @@ namespace Microsoft.AspNetCore.Components.Performance
private class FakeRenderer : Renderer private class FakeRenderer : Renderer
{ {
public FakeRenderer() public FakeRenderer()
: base(new TestServiceProvider(), NullLoggerFactory.Instance, new RendererSynchronizationContext()) : base(new TestServiceProvider(), NullLoggerFactory.Instance)
{ {
} }
public override Dispatcher Dispatcher { get; } = Dispatcher.CreateDefault();
protected override void HandleException(Exception exception) protected override void HandleException(Exception exception)
{ {
throw new NotImplementedException(); throw new NotImplementedException();

View File

@ -85,6 +85,17 @@ namespace Microsoft.AspNetCore.Components
public Microsoft.AspNetCore.Components.UIDataTransferItem[] Items { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public Microsoft.AspNetCore.Components.UIDataTransferItem[] Items { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
public string[] Types { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public string[] Types { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
} }
public abstract partial class Dispatcher
{
protected Dispatcher() { }
public abstract bool CheckAccess();
public static Microsoft.AspNetCore.Components.Dispatcher CreateDefault() { throw null; }
public abstract System.Threading.Tasks.Task InvokeAsync(System.Action workItem);
public abstract System.Threading.Tasks.Task InvokeAsync(System.Func<System.Threading.Tasks.Task> workItem);
public abstract System.Threading.Tasks.Task<TResult> InvokeAsync<TResult>(System.Func<System.Threading.Tasks.Task<TResult>> workItem);
public abstract System.Threading.Tasks.Task<TResult> InvokeAsync<TResult>(System.Func<TResult> workItem);
protected void OnUnhandledException(System.UnhandledExceptionEventArgs e) { }
}
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public readonly partial struct ElementRef public readonly partial struct ElementRef
{ {
@ -303,13 +314,6 @@ namespace Microsoft.AspNetCore.Components
{ {
bool IsConnected { get; } bool IsConnected { get; }
} }
public partial interface IDispatcher
{
System.Threading.Tasks.Task InvokeAsync(System.Action workItem);
System.Threading.Tasks.Task InvokeAsync(System.Func<System.Threading.Tasks.Task> workItem);
System.Threading.Tasks.Task<TResult> InvokeAsync<TResult>(System.Func<System.Threading.Tasks.Task<TResult>> workItem);
System.Threading.Tasks.Task<TResult> InvokeAsync<TResult>(System.Func<TResult> workItem);
}
public partial interface IHandleAfterRender public partial interface IHandleAfterRender
{ {
System.Threading.Tasks.Task OnAfterRenderAsync(); System.Threading.Tasks.Task OnAfterRenderAsync();
@ -406,9 +410,8 @@ namespace Microsoft.AspNetCore.Components
{ {
private readonly object _dummy; private readonly object _dummy;
private readonly int _dummyPrimitive; private readonly int _dummyPrimitive;
public Microsoft.AspNetCore.Components.Dispatcher Dispatcher { get { throw null; } }
public bool IsInitialized { get { throw null; } } public bool IsInitialized { get { throw null; } }
public System.Threading.Tasks.Task InvokeAsync(System.Action workItem) { throw null; }
public System.Threading.Tasks.Task InvokeAsync(System.Func<System.Threading.Tasks.Task> workItem) { throw null; }
public void Render(Microsoft.AspNetCore.Components.RenderFragment renderFragment) { } public void Render(Microsoft.AspNetCore.Components.RenderFragment renderFragment) { }
} }
[System.AttributeUsageAttribute(System.AttributeTargets.Class, AllowMultiple=true, Inherited=false)] [System.AttributeUsageAttribute(System.AttributeTargets.Class, AllowMultiple=true, Inherited=false)]
@ -656,7 +659,8 @@ namespace Microsoft.AspNetCore.Components.Rendering
} }
public partial class HtmlRenderer : Microsoft.AspNetCore.Components.Rendering.Renderer public partial class HtmlRenderer : Microsoft.AspNetCore.Components.Rendering.Renderer
{ {
public HtmlRenderer(System.IServiceProvider serviceProvider, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, Microsoft.AspNetCore.Components.IDispatcher dispatcher, System.Func<string, string> htmlEncoder) : base (default(System.IServiceProvider), default(Microsoft.Extensions.Logging.ILoggerFactory)) { } public HtmlRenderer(System.IServiceProvider serviceProvider, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, System.Func<string, string> htmlEncoder) : base (default(System.IServiceProvider), default(Microsoft.Extensions.Logging.ILoggerFactory)) { }
public override Microsoft.AspNetCore.Components.Dispatcher Dispatcher { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
protected override void HandleException(System.Exception exception) { } protected override void HandleException(System.Exception exception) { }
[System.Diagnostics.DebuggerStepThroughAttribute] [System.Diagnostics.DebuggerStepThroughAttribute]
public System.Threading.Tasks.Task<Microsoft.AspNetCore.Components.Rendering.ComponentRenderedText> RenderComponentAsync(System.Type componentType, Microsoft.AspNetCore.Components.ParameterCollection initialParameters) { throw null; } public System.Threading.Tasks.Task<Microsoft.AspNetCore.Components.Rendering.ComponentRenderedText> RenderComponentAsync(System.Type componentType, Microsoft.AspNetCore.Components.ParameterCollection initialParameters) { throw null; }
@ -675,18 +679,15 @@ namespace Microsoft.AspNetCore.Components.Rendering
public abstract partial class Renderer : System.IDisposable public abstract partial class Renderer : System.IDisposable
{ {
public Renderer(System.IServiceProvider serviceProvider, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { } public Renderer(System.IServiceProvider serviceProvider, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { }
public Renderer(System.IServiceProvider serviceProvider, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, Microsoft.AspNetCore.Components.IDispatcher dispatcher) { } public abstract Microsoft.AspNetCore.Components.Dispatcher Dispatcher { get; }
public event System.UnhandledExceptionEventHandler UnhandledSynchronizationException { add { } remove { } } public event System.UnhandledExceptionEventHandler UnhandledSynchronizationException { add { } remove { } }
protected internal virtual void AddToRenderQueue(int componentId, Microsoft.AspNetCore.Components.RenderFragment renderFragment) { } protected internal virtual void AddToRenderQueue(int componentId, Microsoft.AspNetCore.Components.RenderFragment renderFragment) { }
protected internal int AssignRootComponentId(Microsoft.AspNetCore.Components.IComponent component) { throw null; } protected internal int AssignRootComponentId(Microsoft.AspNetCore.Components.IComponent component) { throw null; }
public static Microsoft.AspNetCore.Components.IDispatcher CreateDefaultDispatcher() { throw null; }
public virtual System.Threading.Tasks.Task DispatchEventAsync(int eventHandlerId, Microsoft.AspNetCore.Components.Rendering.EventFieldInfo fieldInfo, Microsoft.AspNetCore.Components.UIEventArgs eventArgs) { throw null; } public virtual System.Threading.Tasks.Task DispatchEventAsync(int eventHandlerId, Microsoft.AspNetCore.Components.Rendering.EventFieldInfo fieldInfo, Microsoft.AspNetCore.Components.UIEventArgs eventArgs) { throw null; }
public void Dispose() { } public void Dispose() { }
protected virtual void Dispose(bool disposing) { } protected virtual void Dispose(bool disposing) { }
protected abstract void HandleException(System.Exception exception); protected abstract void HandleException(System.Exception exception);
protected Microsoft.AspNetCore.Components.IComponent InstantiateComponent(System.Type componentType) { throw null; } protected Microsoft.AspNetCore.Components.IComponent InstantiateComponent(System.Type componentType) { throw null; }
public virtual System.Threading.Tasks.Task InvokeAsync(System.Action workItem) { throw null; }
public virtual System.Threading.Tasks.Task InvokeAsync(System.Func<System.Threading.Tasks.Task> workItem) { throw null; }
protected System.Threading.Tasks.Task RenderRootComponentAsync(int componentId) { throw null; } protected System.Threading.Tasks.Task RenderRootComponentAsync(int componentId) { throw null; }
[System.Diagnostics.DebuggerStepThroughAttribute] [System.Diagnostics.DebuggerStepThroughAttribute]
protected System.Threading.Tasks.Task RenderRootComponentAsync(int componentId, Microsoft.AspNetCore.Components.ParameterCollection initialParameters) { throw null; } protected System.Threading.Tasks.Task RenderRootComponentAsync(int componentId, Microsoft.AspNetCore.Components.ParameterCollection initialParameters) { throw null; }

View File

@ -148,7 +148,7 @@ namespace Microsoft.AspNetCore.Components
/// </summary> /// </summary>
/// <param name="workItem">The work item to execute.</param> /// <param name="workItem">The work item to execute.</param>
protected Task InvokeAsync(Action workItem) protected Task InvokeAsync(Action workItem)
=> _renderHandle.InvokeAsync(workItem); => _renderHandle.Dispatcher.InvokeAsync(workItem);
/// <summary> /// <summary>
/// Executes the supplied work item on the associated renderer's /// Executes the supplied work item on the associated renderer's
@ -156,7 +156,7 @@ namespace Microsoft.AspNetCore.Components
/// </summary> /// </summary>
/// <param name="workItem">The work item to execute.</param> /// <param name="workItem">The work item to execute.</param>
protected Task InvokeAsync(Func<Task> workItem) protected Task InvokeAsync(Func<Task> workItem)
=> _renderHandle.InvokeAsync(workItem); => _renderHandle.Dispatcher.InvokeAsync(workItem);
void IComponent.Configure(RenderHandle renderHandle) void IComponent.Configure(RenderHandle renderHandle)
{ {

View File

@ -10,34 +10,66 @@ namespace Microsoft.AspNetCore.Components
/// <summary> /// <summary>
/// Dispatches external actions to be executed on the context of a <see cref="Renderer"/>. /// Dispatches external actions to be executed on the context of a <see cref="Renderer"/>.
/// </summary> /// </summary>
public interface IDispatcher public abstract class Dispatcher
{ {
/// <summary>
/// Creates a default instance of <see cref="Dispatcher"/>.
/// </summary>
/// <returns>A <see cref="Dispatcher"/> instance.</returns>
public static Dispatcher CreateDefault() => new RendererSynchronizationContextDispatcher();
/// <summary>
/// Provides notifications of unhandled exceptions that occur within the dispatcher.
/// </summary>
internal event UnhandledExceptionEventHandler UnhandledException;
/// <summary>
/// Returns a value that determines whether using the dispatcher to invoke a work item is required
/// from the current context.
/// </summary>
/// <returns><c>true</c> if invoking is required, otherwise <c>false</c>.</returns>
public abstract bool CheckAccess();
/// <summary> /// <summary>
/// Invokes the given <see cref="Action"/> in the context of the associated <see cref="Renderer"/>. /// Invokes the given <see cref="Action"/> in the context of the associated <see cref="Renderer"/>.
/// </summary> /// </summary>
/// <param name="workItem">The action to execute.</param> /// <param name="workItem">The action to execute.</param>
/// <returns>A <see cref="Task"/> that will be completed when the action has finished executing.</returns> /// <returns>A <see cref="Task"/> that will be completed when the action has finished executing.</returns>
Task InvokeAsync(Action workItem); public abstract Task InvokeAsync(Action workItem);
/// <summary> /// <summary>
/// Invokes the given <see cref="Func{TResult}"/> in the context of the associated <see cref="Renderer"/>. /// Invokes the given <see cref="Func{TResult}"/> in the context of the associated <see cref="Renderer"/>.
/// </summary> /// </summary>
/// <param name="workItem">The asynchronous action to execute.</param> /// <param name="workItem">The asynchronous action to execute.</param>
/// <returns>A <see cref="Task"/> that will be completed when the action has finished executing.</returns> /// <returns>A <see cref="Task"/> that will be completed when the action has finished executing.</returns>
Task InvokeAsync(Func<Task> workItem); public abstract Task InvokeAsync(Func<Task> workItem);
/// <summary> /// <summary>
/// Invokes the given <see cref="Func{TResult}"/> in the context of the associated <see cref="Renderer"/>. /// Invokes the given <see cref="Func{TResult}"/> in the context of the associated <see cref="Renderer"/>.
/// </summary> /// </summary>
/// <param name="workItem">The function to execute.</param> /// <param name="workItem">The function to execute.</param>
/// <returns>A <see cref="Task{TResult}"/> that will be completed when the function has finished executing.</returns> /// <returns>A <see cref="Task{TResult}"/> that will be completed when the function has finished executing.</returns>
Task<TResult> InvokeAsync<TResult>(Func<TResult> workItem); public abstract Task<TResult> InvokeAsync<TResult>(Func<TResult> workItem);
/// <summary> /// <summary>
/// Invokes the given <see cref="Func{TResult}"/> in the context of the associated <see cref="Renderer"/>. /// Invokes the given <see cref="Func{TResult}"/> in the context of the associated <see cref="Renderer"/>.
/// </summary> /// </summary>
/// <param name="workItem">The asynchronous function to execute.</param> /// <param name="workItem">The asynchronous function to execute.</param>
/// <returns>A <see cref="Task{TResult}"/> that will be completed when the function has finished executing.</returns> /// <returns>A <see cref="Task{TResult}"/> that will be completed when the function has finished executing.</returns>
Task<TResult> InvokeAsync<TResult>(Func<Task<TResult>> workItem); public abstract Task<TResult> InvokeAsync<TResult>(Func<Task<TResult>> workItem);
/// <summary>
/// Called to notify listeners of an unhandled exception.
/// </summary>
/// <param name="e">The <see cref="UnhandledExceptionEventArgs"/>.</param>
protected void OnUnhandledException(UnhandledExceptionEventArgs e)
{
if (e is null)
{
throw new ArgumentNullException(nameof(e));
}
UnhandledException?.Invoke(this, e);
}
} }
} }

View File

@ -2,13 +2,12 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Rendering; using Microsoft.AspNetCore.Components.Rendering;
namespace Microsoft.AspNetCore.Components namespace Microsoft.AspNetCore.Components
{ {
/// <summary> /// <summary>
/// Allows a component to notify the renderer that it should be rendered. /// Allows a component to interact with its renderer.
/// </summary> /// </summary>
public readonly struct RenderHandle public readonly struct RenderHandle
{ {
@ -21,9 +20,25 @@ namespace Microsoft.AspNetCore.Components
_componentId = componentId; _componentId = componentId;
} }
/// <summary>
/// Gets the <see cref="Microsoft.AspNetCore.Components.Dispatcher" /> associated with the component.
/// </summary>
public Dispatcher Dispatcher
{
get
{
if (_renderer == null)
{
ThrowNotInitialized();
}
return _renderer.Dispatcher;
}
}
/// <summary> /// <summary>
/// Gets a value that indicates whether the <see cref="RenderHandle"/> has been /// Gets a value that indicates whether the <see cref="RenderHandle"/> has been
/// initialised and is ready to use. /// initialized and is ready to use.
/// </summary> /// </summary>
public bool IsInitialized public bool IsInitialized
=> _renderer != null; => _renderer != null;
@ -36,38 +51,15 @@ namespace Microsoft.AspNetCore.Components
{ {
if (_renderer == null) if (_renderer == null)
{ {
throw new InvalidOperationException("The render handle is not yet assigned."); ThrowNotInitialized();
} }
_renderer.AddToRenderQueue(_componentId, renderFragment); _renderer.AddToRenderQueue(_componentId, renderFragment);
} }
/// <summary> private static void ThrowNotInitialized()
/// Executes the supplied work item on the renderer's
/// synchronization context.
/// </summary>
/// <param name="workItem">The work item to execute.</param>
public Task InvokeAsync(Action workItem)
{ {
if (_renderer == null) throw new InvalidOperationException("The render handle is not yet assigned.");
{
throw new InvalidOperationException("The render handle is not yet assigned.");
}
return _renderer.InvokeAsync(workItem);
}
/// <summary>
/// Executes the supplied work item on the renderer's
/// synchronization context.
/// </summary>
/// <param name="workItem">The work item to execute.</param>
public Task InvokeAsync(Func<Task> workItem)
{
if (_renderer == null)
{
throw new InvalidOperationException("The render handle is not yet assigned.");
}
return _renderer.InvokeAsync(workItem);
} }
} }
} }

View File

@ -4,6 +4,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.ExceptionServices; using System.Runtime.ExceptionServices;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.RenderTree; using Microsoft.AspNetCore.Components.RenderTree;
@ -28,14 +29,15 @@ namespace Microsoft.AspNetCore.Components.Rendering
/// </summary> /// </summary>
/// <param name="serviceProvider">The <see cref="IServiceProvider"/> to use to instantiate components.</param> /// <param name="serviceProvider">The <see cref="IServiceProvider"/> to use to instantiate components.</param>
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param> /// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
/// <param name="dispatcher">The <see cref="IDispatcher"/> to be for invoking user actions into the <see cref="Renderer"/> context.</param>
/// <param name="htmlEncoder">A <see cref="Func{T, TResult}"/> that will HTML encode the given string.</param> /// <param name="htmlEncoder">A <see cref="Func{T, TResult}"/> that will HTML encode the given string.</param>
public HtmlRenderer(IServiceProvider serviceProvider, ILoggerFactory loggerFactory, IDispatcher dispatcher, Func<string, string> htmlEncoder) public HtmlRenderer(IServiceProvider serviceProvider, ILoggerFactory loggerFactory, Func<string, string> htmlEncoder)
: base(serviceProvider, loggerFactory, dispatcher) : base(serviceProvider, loggerFactory)
{ {
_htmlEncoder = htmlEncoder; _htmlEncoder = htmlEncoder;
} }
public override Dispatcher Dispatcher { get; } = Dispatcher.CreateDefault();
/// <inheritdoc /> /// <inheritdoc />
protected override Task UpdateDisplayAsync(in RenderBatch renderBatch) protected override Task UpdateDisplayAsync(in RenderBatch renderBatch)
{ {

View File

@ -22,7 +22,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
private readonly RenderBatchBuilder _batchBuilder = new RenderBatchBuilder(); private readonly RenderBatchBuilder _batchBuilder = new RenderBatchBuilder();
private readonly Dictionary<int, EventCallback> _eventBindings = new Dictionary<int, EventCallback>(); private readonly Dictionary<int, EventCallback> _eventBindings = new Dictionary<int, EventCallback>();
private readonly Dictionary<int, int> _eventHandlerIdReplacements = new Dictionary<int, int>(); private readonly Dictionary<int, int> _eventHandlerIdReplacements = new Dictionary<int, int>();
private readonly IDispatcher _dispatcher;
private readonly ILogger<Renderer> _logger; private readonly ILogger<Renderer> _logger;
private int _nextComponentId = 0; // TODO: change to 'long' when Mono .NET->JS interop supports it private int _nextComponentId = 0; // TODO: change to 'long' when Mono .NET->JS interop supports it
@ -37,19 +36,11 @@ namespace Microsoft.AspNetCore.Components.Rendering
{ {
add add
{ {
if (!(_dispatcher is RendererSynchronizationContext rendererSynchronizationContext)) Dispatcher.UnhandledException += value;
{
return;
}
rendererSynchronizationContext.UnhandledException += value;
} }
remove remove
{ {
if (!(_dispatcher is RendererSynchronizationContext rendererSynchronizationContext)) Dispatcher.UnhandledException -= value;
{
return;
}
rendererSynchronizationContext.UnhandledException -= value;
} }
} }
@ -72,25 +63,13 @@ namespace Microsoft.AspNetCore.Components.Rendering
_componentFactory = new ComponentFactory(serviceProvider); _componentFactory = new ComponentFactory(serviceProvider);
_logger = loggerFactory.CreateLogger<Renderer>(); _logger = loggerFactory.CreateLogger<Renderer>();
_componentFactory = new ComponentFactory(serviceProvider);
} }
/// <summary> /// <summary>
/// Constructs an instance of <see cref="Renderer"/>. /// Gets the <see cref="Microsoft.AspNetCore.Components.Dispatcher" /> associated with this <see cref="Renderer" />.
/// </summary> /// </summary>
/// <param name="serviceProvider">The <see cref="IServiceProvider"/> to be used when initializing components.</param> public abstract Dispatcher Dispatcher { get; }
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
/// <param name="dispatcher">The <see cref="IDispatcher"/> to be for invoking user actions into the <see cref="Renderer"/> context.</param>
public Renderer(IServiceProvider serviceProvider, ILoggerFactory loggerFactory, IDispatcher dispatcher)
: this(serviceProvider, loggerFactory)
{
_dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
}
/// <summary>
/// Creates an <see cref="IDispatcher"/> that can be used with one or more <see cref="Renderer"/>.
/// </summary>
/// <returns>The <see cref="IDispatcher"/>.</returns>
public static IDispatcher CreateDefaultDispatcher() => new RendererSynchronizationContext();
/// <summary> /// <summary>
/// Constructs a new component of the specified type. /// Constructs a new component of the specified type.
@ -272,58 +251,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
return GetErrorHandledTask(task); return GetErrorHandledTask(task);
} }
/// <summary>
/// Executes the supplied work item on the renderer's
/// synchronization context.
/// </summary>
/// <param name="workItem">The work item to execute.</param>
public virtual Task InvokeAsync(Action workItem)
{
// This is for example when we run on a system with a single thread, like WebAssembly.
if (_dispatcher == null)
{
workItem();
return Task.CompletedTask;
}
if (SynchronizationContext.Current == _dispatcher)
{
// This is an optimization for when the dispatcher is also a syncronization context, like in the default case.
// No need to dispatch. Avoid deadlock by invoking directly.
workItem();
return Task.CompletedTask;
}
else
{
return _dispatcher.InvokeAsync(workItem);
}
}
/// <summary>
/// Executes the supplied work item on the renderer's
/// synchronization context.
/// </summary>
/// <param name="workItem">The work item to execute.</param>
public virtual Task InvokeAsync(Func<Task> workItem)
{
// This is for example when we run on a system with a single thread, like WebAssembly.
if (_dispatcher == null)
{
return workItem();
}
if (SynchronizationContext.Current == _dispatcher)
{
// This is an optimization for when the dispatcher is also a syncronization context, like in the default case.
// No need to dispatch. Avoid deadlock by invoking directly.
return workItem();
}
else
{
return _dispatcher.InvokeAsync(workItem);
}
}
internal void InstantiateChildComponentOnFrame(ref RenderTreeFrame frame, int parentComponentId) internal void InstantiateChildComponentOnFrame(ref RenderTreeFrame frame, int parentComponentId)
{ {
if (frame.FrameType != RenderTreeFrameType.Component) if (frame.FrameType != RenderTreeFrameType.Component)
@ -448,17 +375,16 @@ namespace Microsoft.AspNetCore.Components.Rendering
private void EnsureSynchronizationContext() private void EnsureSynchronizationContext()
{ {
// When the IDispatcher is a synchronization context // Render operations are not thread-safe, so they need to be serialized by the dispatcher.
// Render operations are not thread-safe, so they need to be serialized.
// Plus, any other logic that mutates state accessed during rendering also // Plus, any other logic that mutates state accessed during rendering also
// needs not to run concurrently with rendering so should be dispatched to // needs not to run concurrently with rendering so should be dispatched to
// the renderer's sync context. // the renderer's sync context.
if (_dispatcher is SynchronizationContext synchronizationContext && SynchronizationContext.Current != synchronizationContext) if (!Dispatcher.CheckAccess())
{ {
throw new InvalidOperationException( throw new InvalidOperationException(
"The current thread is not associated with the renderer's synchronization context. " + "The current thread is not associated with the Dispatcher. " +
"Use Invoke() or InvokeAsync() to switch execution to the renderer's synchronization " + "Use Invoke() or InvokeAsync() to switch execution to the Dispatcher when " +
"context when triggering rendering or modifying any state accessed during rendering."); "triggering rendering or modifying any state accessed during rendering.");
} }
} }

View File

@ -9,7 +9,7 @@ using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Components.Rendering namespace Microsoft.AspNetCore.Components.Rendering
{ {
[DebuggerDisplay("{_state,nq}")] [DebuggerDisplay("{_state,nq}")]
internal class RendererSynchronizationContext : SynchronizationContext, IDispatcher internal class RendererSynchronizationContext : SynchronizationContext
{ {
private static readonly ContextCallback ExecutionContextThunk = (object state) => private static readonly ContextCallback ExecutionContextThunk = (object state) =>
{ {

View File

@ -0,0 +1,66 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Components.Rendering
{
internal class RendererSynchronizationContextDispatcher : Dispatcher
{
private readonly RendererSynchronizationContext _context;
public RendererSynchronizationContextDispatcher()
{
_context = new RendererSynchronizationContext();
_context.UnhandledException += (sender, e) =>
{
OnUnhandledException(e);
};
}
public override bool CheckAccess() => SynchronizationContext.Current == _context;
public override Task InvokeAsync(Action workItem)
{
if (CheckAccess())
{
workItem();
return Task.CompletedTask;
}
return _context.InvokeAsync(workItem);
}
public override Task InvokeAsync(Func<Task> workItem)
{
if (CheckAccess())
{
return workItem();
}
return _context.InvokeAsync(workItem);
}
public override Task<TResult> InvokeAsync<TResult>(Func<TResult> workItem)
{
if (CheckAccess())
{
return Task.FromResult(workItem());
}
return _context.InvokeAsync<TResult>(workItem);
}
public override Task<TResult> InvokeAsync<TResult>(Func<Task<TResult>> workItem)
{
if (CheckAccess())
{
return workItem();
}
return _context.InvokeAsync<TResult>(workItem);
}
}
}

View File

@ -386,7 +386,7 @@ namespace Microsoft.AspNetCore.Components.Test
supplierParams.Add("Name", name); supplierParams.Add("Name", name);
} }
renderer.InvokeAsync((Action)(() => supplier.SetParametersAsync(ParameterCollection.FromDictionary(supplierParams)))); renderer.Dispatcher.InvokeAsync((Action)(() => supplier.SetParametersAsync(ParameterCollection.FromDictionary(supplierParams))));
return supplier; return supplier;
} }

View File

@ -27,7 +27,7 @@ namespace Microsoft.AspNetCore.Components.Test
public void DisplaysComponentInsideLayout() public void DisplaysComponentInsideLayout()
{ {
// Arrange/Act // Arrange/Act
_renderer.InvokeAsync(() => _pageDisplayComponent.SetParametersAsync(ParameterCollection.FromDictionary(new Dictionary<string, object> _renderer.Dispatcher.InvokeAsync(() => _pageDisplayComponent.SetParametersAsync(ParameterCollection.FromDictionary(new Dictionary<string, object>
{ {
{ nameof(PageDisplay.Page), typeof(ComponentWithLayout) } { nameof(PageDisplay.Page), typeof(ComponentWithLayout) }
}))); })));
@ -84,7 +84,7 @@ namespace Microsoft.AspNetCore.Components.Test
public void DisplaysComponentInsideNestedLayout() public void DisplaysComponentInsideNestedLayout()
{ {
// Arrange/Act // Arrange/Act
_renderer.InvokeAsync(() => _pageDisplayComponent.SetParametersAsync(ParameterCollection.FromDictionary(new Dictionary<string, object> _renderer.Dispatcher.InvokeAsync(() => _pageDisplayComponent.SetParametersAsync(ParameterCollection.FromDictionary(new Dictionary<string, object>
{ {
{ nameof(PageDisplay.Page), typeof(ComponentWithNestedLayout) } { nameof(PageDisplay.Page), typeof(ComponentWithNestedLayout) }
}))); })));
@ -111,13 +111,13 @@ namespace Microsoft.AspNetCore.Components.Test
public void CanChangeDisplayedPageWithSameLayout() public void CanChangeDisplayedPageWithSameLayout()
{ {
// Arrange // Arrange
_renderer.InvokeAsync(() => _pageDisplayComponent.SetParametersAsync(ParameterCollection.FromDictionary(new Dictionary<string, object> _renderer.Dispatcher.InvokeAsync(() => _pageDisplayComponent.SetParametersAsync(ParameterCollection.FromDictionary(new Dictionary<string, object>
{ {
{ nameof(PageDisplay.Page), typeof(ComponentWithLayout) } { nameof(PageDisplay.Page), typeof(ComponentWithLayout) }
}))); })));
// Act // Act
_renderer.InvokeAsync(() => _pageDisplayComponent.SetParametersAsync(ParameterCollection.FromDictionary(new Dictionary<string, object> _renderer.Dispatcher.InvokeAsync(() => _pageDisplayComponent.SetParametersAsync(ParameterCollection.FromDictionary(new Dictionary<string, object>
{ {
{ nameof(PageDisplay.Page), typeof(DifferentComponentWithLayout) } { nameof(PageDisplay.Page), typeof(DifferentComponentWithLayout) }
}))); })));
@ -162,13 +162,13 @@ namespace Microsoft.AspNetCore.Components.Test
public void CanChangeDisplayedPageWithDifferentLayout() public void CanChangeDisplayedPageWithDifferentLayout()
{ {
// Arrange // Arrange
_renderer.InvokeAsync(() => _pageDisplayComponent.SetParametersAsync(ParameterCollection.FromDictionary(new Dictionary<string, object> _renderer.Dispatcher.InvokeAsync(() => _pageDisplayComponent.SetParametersAsync(ParameterCollection.FromDictionary(new Dictionary<string, object>
{ {
{ nameof(PageDisplay.Page), typeof(ComponentWithLayout) } { nameof(PageDisplay.Page), typeof(ComponentWithLayout) }
}))); })));
// Act // Act
_renderer.InvokeAsync(() => _pageDisplayComponent.SetParametersAsync(ParameterCollection.FromDictionary(new Dictionary<string, object> _renderer.Dispatcher.InvokeAsync(() => _pageDisplayComponent.SetParametersAsync(ParameterCollection.FromDictionary(new Dictionary<string, object>
{ {
{ nameof(PageDisplay.Page), typeof(ComponentWithNestedLayout) } { nameof(PageDisplay.Page), typeof(ComponentWithNestedLayout) }
}))); })));

View File

@ -1795,10 +1795,12 @@ namespace Microsoft.AspNetCore.Components.Test
private class TestRenderer : Renderer private class TestRenderer : Renderer
{ {
public TestRenderer() : base(new TestServiceProvider(), NullLoggerFactory.Instance, new RendererSynchronizationContext()) public TestRenderer() : base(new TestServiceProvider(), NullLoggerFactory.Instance)
{ {
} }
public override Dispatcher Dispatcher { get; } = Dispatcher.CreateDefault();
protected override void HandleException(Exception exception) protected override void HandleException(Exception exception)
=> throw new NotImplementedException(); => throw new NotImplementedException();

View File

@ -2208,10 +2208,12 @@ namespace Microsoft.AspNetCore.Components.Test
private class FakeRenderer : Renderer private class FakeRenderer : Renderer
{ {
public FakeRenderer() : base(new TestServiceProvider(), NullLoggerFactory.Instance, new RendererSynchronizationContext()) public FakeRenderer() : base(new TestServiceProvider(), NullLoggerFactory.Instance)
{ {
} }
public override Dispatcher Dispatcher { get; } = Dispatcher.CreateDefault();
protected override void HandleException(Exception exception) protected override void HandleException(Exception exception)
=> throw new NotImplementedException(); => throw new NotImplementedException();

View File

@ -186,7 +186,7 @@ namespace Microsoft.AspNetCore.Components.Test
// Act // Act
var componentId = renderer.AssignRootComponentId(component); var componentId = renderer.AssignRootComponentId(component);
var renderTask = renderer.InvokeAsync(() => renderer.RenderRootComponentAsync(componentId)); var renderTask = renderer.Dispatcher.InvokeAsync(() => renderer.RenderRootComponentAsync(componentId));
// Assert // Assert
Assert.False(renderTask.IsCompleted); Assert.False(renderTask.IsCompleted);
@ -239,7 +239,7 @@ namespace Microsoft.AspNetCore.Components.Test
// Act/Assert // Act/Assert
var componentId = renderer.AssignRootComponentId(component); var componentId = renderer.AssignRootComponentId(component);
var log = new ConcurrentQueue<(int id, NestedAsyncComponent.EventType @event)>(); var log = new ConcurrentQueue<(int id, NestedAsyncComponent.EventType @event)>();
await renderer.InvokeAsync(() => renderer.RenderRootComponentAsync(componentId, ParameterCollection.FromDictionary(new Dictionary<string, object> await renderer.Dispatcher.InvokeAsync(() => renderer.RenderRootComponentAsync(componentId, ParameterCollection.FromDictionary(new Dictionary<string, object>
{ {
[EventActionsName] = new Dictionary<int, IList<NestedAsyncComponent.ExecutionAction>> [EventActionsName] = new Dictionary<int, IList<NestedAsyncComponent.ExecutionAction>>
{ {
@ -283,7 +283,7 @@ namespace Microsoft.AspNetCore.Components.Test
// Act/Assert // Act/Assert
var componentId = renderer.AssignRootComponentId(component); var componentId = renderer.AssignRootComponentId(component);
var log = new ConcurrentQueue<(int id, NestedAsyncComponent.EventType @event)>(); var log = new ConcurrentQueue<(int id, NestedAsyncComponent.EventType @event)>();
await renderer.InvokeAsync(() => renderer.RenderRootComponentAsync(componentId, ParameterCollection.FromDictionary(new Dictionary<string, object> await renderer.Dispatcher.InvokeAsync(() => renderer.RenderRootComponentAsync(componentId, ParameterCollection.FromDictionary(new Dictionary<string, object>
{ {
[EventActionsName] = new Dictionary<int, IList<NestedAsyncComponent.ExecutionAction>> [EventActionsName] = new Dictionary<int, IList<NestedAsyncComponent.ExecutionAction>>
{ {
@ -327,7 +327,7 @@ namespace Microsoft.AspNetCore.Components.Test
// Act/Assert // Act/Assert
var componentId = renderer.AssignRootComponentId(component); var componentId = renderer.AssignRootComponentId(component);
var log = new ConcurrentQueue<(int id, NestedAsyncComponent.EventType @event)>(); var log = new ConcurrentQueue<(int id, NestedAsyncComponent.EventType @event)>();
await renderer.InvokeAsync(() => renderer.RenderRootComponentAsync(componentId, ParameterCollection.FromDictionary(new Dictionary<string, object> await renderer.Dispatcher.InvokeAsync(() => renderer.RenderRootComponentAsync(componentId, ParameterCollection.FromDictionary(new Dictionary<string, object>
{ {
[EventActionsName] = new Dictionary<int, IList<NestedAsyncComponent.ExecutionAction>> [EventActionsName] = new Dictionary<int, IList<NestedAsyncComponent.ExecutionAction>>
{ {
@ -371,7 +371,7 @@ namespace Microsoft.AspNetCore.Components.Test
// Act/Assert // Act/Assert
var componentId = renderer.AssignRootComponentId(component); var componentId = renderer.AssignRootComponentId(component);
var log = new ConcurrentQueue<(int id, NestedAsyncComponent.EventType @event)>(); var log = new ConcurrentQueue<(int id, NestedAsyncComponent.EventType @event)>();
await renderer.InvokeAsync(() => renderer.RenderRootComponentAsync(componentId, ParameterCollection.FromDictionary(new Dictionary<string, object> await renderer.Dispatcher.InvokeAsync(() => renderer.RenderRootComponentAsync(componentId, ParameterCollection.FromDictionary(new Dictionary<string, object>
{ {
[EventActionsName] = new Dictionary<int, IList<NestedAsyncComponent.ExecutionAction>> [EventActionsName] = new Dictionary<int, IList<NestedAsyncComponent.ExecutionAction>>
{ {
@ -2017,7 +2017,7 @@ namespace Microsoft.AspNetCore.Components.Test
// Act/Assert: If a disposed component requests a render, it's a no-op // Act/Assert: If a disposed component requests a render, it's a no-op
var renderHandle = ((FakeComponent)childComponent3).RenderHandle; var renderHandle = ((FakeComponent)childComponent3).RenderHandle;
renderHandle.InvokeAsync(() => renderHandle.Render(builder renderHandle.Dispatcher.InvokeAsync(() => renderHandle.Render(builder
=> throw new NotImplementedException("Should not be invoked"))); => throw new NotImplementedException("Should not be invoked")));
Assert.Equal(2, renderer.Batches.Count); Assert.Equal(2, renderer.Batches.Count);
} }
@ -2906,7 +2906,7 @@ namespace Microsoft.AspNetCore.Components.Test
var asyncExceptionTcs = new TaskCompletionSource<object>(); var asyncExceptionTcs = new TaskCompletionSource<object>();
taskToAwait = asyncExceptionTcs.Task; taskToAwait = asyncExceptionTcs.Task;
await renderer.InvokeAsync(component.TriggerRender); await renderer.Dispatcher.InvokeAsync(component.TriggerRender);
// Act // Act
var exception = new InvalidOperationException(); var exception = new InvalidOperationException();
@ -3373,10 +3373,12 @@ namespace Microsoft.AspNetCore.Components.Test
private class NoOpRenderer : Renderer private class NoOpRenderer : Renderer
{ {
public NoOpRenderer() : base(new TestServiceProvider(), NullLoggerFactory.Instance, new RendererSynchronizationContext()) public NoOpRenderer() : base(new TestServiceProvider(), NullLoggerFactory.Instance)
{ {
} }
public override Dispatcher Dispatcher { get; } = Dispatcher.CreateDefault();
public new int AssignRootComponentId(IComponent component) public new int AssignRootComponentId(IComponent component)
=> base.AssignRootComponentId(component); => base.AssignRootComponentId(component);
@ -3410,7 +3412,7 @@ namespace Microsoft.AspNetCore.Components.Test
public void TriggerRender() public void TriggerRender()
{ {
var t = _renderHandle.InvokeAsync(() => _renderHandle.Render(_renderFragment)); var t = _renderHandle.Dispatcher.InvokeAsync(() => _renderHandle.Render(_renderFragment));
// This should always be run synchronously // This should always be run synchronously
Assert.True(t.IsCompleted); Assert.True(t.IsCompleted);
if (t.IsFaulted) if (t.IsFaulted)
@ -3660,7 +3662,7 @@ namespace Microsoft.AspNetCore.Components.Test
{ {
foreach (var renderHandle in _renderHandles) foreach (var renderHandle in _renderHandles)
{ {
renderHandle.InvokeAsync(() => renderHandle.Render(builder => renderHandle.Dispatcher.InvokeAsync(() => renderHandle.Render(builder =>
{ {
builder.AddContent(0, $"Hello from {nameof(MultiRendererComponent)}"); builder.AddContent(0, $"Hello from {nameof(MultiRendererComponent)}");
})); }));
@ -3821,7 +3823,7 @@ namespace Microsoft.AspNetCore.Components.Test
return TriggerRenderAsync(); return TriggerRenderAsync();
} }
public Task TriggerRenderAsync() => _renderHandle.InvokeAsync(() => _renderHandle.Render(RenderFragment)); public Task TriggerRenderAsync() => _renderHandle.Dispatcher.InvokeAsync(() => _renderHandle.Render(RenderFragment));
} }
private void AssertStream(int expectedId, (int id, NestedAsyncComponent.EventType @event)[] logStream) private void AssertStream(int expectedId, (int id, NestedAsyncComponent.EventType @event)[] logStream)

View File

@ -15,7 +15,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
public abstract class HtmlRendererTestBase public abstract class HtmlRendererTestBase
{ {
protected readonly Func<string, string> _encoder = (string t) => HtmlEncoder.Default.Encode(t); protected readonly Func<string, string> _encoder = (string t) => HtmlEncoder.Default.Encode(t);
protected readonly IDispatcher Dispatcher = Renderer.CreateDefaultDispatcher();
protected abstract HtmlRenderer GetHtmlRenderer(IServiceProvider serviceProvider); protected abstract HtmlRenderer GetHtmlRenderer(IServiceProvider serviceProvider);
@ -33,7 +32,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
var htmlRenderer = GetHtmlRenderer(serviceProvider); var htmlRenderer = GetHtmlRenderer(serviceProvider);
// Act // Act
var result = GetResult(Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<TestComponent>(ParameterCollection.Empty))); var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<TestComponent>(ParameterCollection.Empty)));
// Assert // Assert
Assert.Equal(expectedHtml, result); Assert.Equal(expectedHtml, result);
@ -43,7 +42,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
public void RenderComponentAsync_CanRenderSimpleComponent() public void RenderComponentAsync_CanRenderSimpleComponent()
{ {
// Arrange // Arrange
var dispatcher = Renderer.CreateDefaultDispatcher();
var expectedHtml = new[] { "<", "p", ">", "Hello world!", "</", "p", ">" }; var expectedHtml = new[] { "<", "p", ">", "Hello world!", "</", "p", ">" };
var serviceProvider = new ServiceCollection().AddSingleton(new RenderFragment(rtb => var serviceProvider = new ServiceCollection().AddSingleton(new RenderFragment(rtb =>
{ {
@ -54,7 +52,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
var htmlRenderer = GetHtmlRenderer(serviceProvider); var htmlRenderer = GetHtmlRenderer(serviceProvider);
// Act // Act
var result = GetResult(Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<TestComponent>(ParameterCollection.Empty))); var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<TestComponent>(ParameterCollection.Empty)));
// Assert // Assert
Assert.Equal(expectedHtml, result); Assert.Equal(expectedHtml, result);
@ -64,7 +62,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
public void RenderComponentAsync_HtmlEncodesContent() public void RenderComponentAsync_HtmlEncodesContent()
{ {
// Arrange // Arrange
var dispatcher = Renderer.CreateDefaultDispatcher();
var expectedHtml = new[] { "<", "p", ">", "&lt;Hello world!&gt;", "</", "p", ">" }; var expectedHtml = new[] { "<", "p", ">", "&lt;Hello world!&gt;", "</", "p", ">" };
var serviceProvider = new ServiceCollection().AddSingleton(new RenderFragment(rtb => var serviceProvider = new ServiceCollection().AddSingleton(new RenderFragment(rtb =>
{ {
@ -75,7 +72,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
var htmlRenderer = GetHtmlRenderer(serviceProvider); var htmlRenderer = GetHtmlRenderer(serviceProvider);
// Act // Act
var result = GetResult(Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<TestComponent>(ParameterCollection.Empty))); var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<TestComponent>(ParameterCollection.Empty)));
// Assert // Assert
Assert.Equal(expectedHtml, result); Assert.Equal(expectedHtml, result);
@ -96,7 +93,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
var htmlRenderer = GetHtmlRenderer(serviceProvider); var htmlRenderer = GetHtmlRenderer(serviceProvider);
// Act // Act
var result = GetResult(Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<TestComponent>(ParameterCollection.Empty))); var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<TestComponent>(ParameterCollection.Empty)));
// Assert // Assert
Assert.Equal(expectedHtml, result); Assert.Equal(expectedHtml, result);
@ -119,7 +116,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
var htmlRenderer = GetHtmlRenderer(serviceProvider); var htmlRenderer = GetHtmlRenderer(serviceProvider);
// Act // Act
var result = GetResult(Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<TestComponent>(ParameterCollection.Empty))); var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<TestComponent>(ParameterCollection.Empty)));
// Assert // Assert
Assert.Equal(expectedHtml, result); Assert.Equal(expectedHtml, result);
@ -153,7 +150,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
var htmlRenderer = GetHtmlRenderer(serviceProvider); var htmlRenderer = GetHtmlRenderer(serviceProvider);
// Act // Act
var result = GetResult(Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<TestComponent>(ParameterCollection.Empty))); var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<TestComponent>(ParameterCollection.Empty)));
// Assert // Assert
Assert.Equal(expectedHtml, result); Assert.Equal(expectedHtml, result);
@ -175,7 +172,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
var htmlRenderer = GetHtmlRenderer(serviceProvider); var htmlRenderer = GetHtmlRenderer(serviceProvider);
// Act // Act
var result = GetResult(Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<TestComponent>(ParameterCollection.Empty))); var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<TestComponent>(ParameterCollection.Empty)));
// Assert // Assert
Assert.Equal(expectedHtml, result); Assert.Equal(expectedHtml, result);
@ -196,7 +193,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
var htmlRenderer = GetHtmlRenderer(serviceProvider); var htmlRenderer = GetHtmlRenderer(serviceProvider);
// Act // Act
var result = GetResult(Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<TestComponent>(ParameterCollection.Empty))); var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<TestComponent>(ParameterCollection.Empty)));
// Assert // Assert
Assert.Equal(expectedHtml, result); Assert.Equal(expectedHtml, result);
@ -217,7 +214,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
var htmlRenderer = GetHtmlRenderer(serviceProvider); var htmlRenderer = GetHtmlRenderer(serviceProvider);
// Act // Act
var result = GetResult(Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<TestComponent>(ParameterCollection.Empty))); var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<TestComponent>(ParameterCollection.Empty)));
// Assert // Assert
Assert.Equal(expectedHtml, result); Assert.Equal(expectedHtml, result);
@ -240,7 +237,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
var htmlRenderer = GetHtmlRenderer(serviceProvider); var htmlRenderer = GetHtmlRenderer(serviceProvider);
// Act // Act
var result = GetResult(Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<TestComponent>(ParameterCollection.Empty))); var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<TestComponent>(ParameterCollection.Empty)));
// Assert // Assert
Assert.Equal(expectedHtml, result); Assert.Equal(expectedHtml, result);
@ -270,7 +267,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
var htmlRenderer = GetHtmlRenderer(serviceProvider); var htmlRenderer = GetHtmlRenderer(serviceProvider);
// Act // Act
var result = GetResult(Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<TestComponent>(ParameterCollection.Empty))); var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<TestComponent>(ParameterCollection.Empty)));
// Assert // Assert
Assert.Equal(expectedHtml, result); Assert.Equal(expectedHtml, result);
@ -299,7 +296,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
var htmlRenderer = GetHtmlRenderer(serviceProvider); var htmlRenderer = GetHtmlRenderer(serviceProvider);
// Act // Act
var result = GetResult(Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<TestComponent>(ParameterCollection.Empty))); var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<TestComponent>(ParameterCollection.Empty)));
// Assert // Assert
Assert.Equal(expectedHtml, result); Assert.Equal(expectedHtml, result);
@ -329,7 +326,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
var htmlRenderer = GetHtmlRenderer(serviceProvider); var htmlRenderer = GetHtmlRenderer(serviceProvider);
// Act // Act
var result = GetResult(Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<TestComponent>(ParameterCollection.Empty))); var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<TestComponent>(ParameterCollection.Empty)));
// Assert // Assert
Assert.Equal(expectedHtml, result); Assert.Equal(expectedHtml, result);
@ -360,7 +357,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
Action<UIChangeEventArgs> change = (UIChangeEventArgs changeArgs) => throw new InvalidOperationException(); Action<UIChangeEventArgs> change = (UIChangeEventArgs changeArgs) => throw new InvalidOperationException();
// Act // Act
var result = GetResult(Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<ComponentWithParameters>( var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<ComponentWithParameters>(
new ParameterCollection(new[] { new ParameterCollection(new[] {
RenderTreeFrame.Element(0,string.Empty), RenderTreeFrame.Element(0,string.Empty),
RenderTreeFrame.Attribute(1,"update",change), RenderTreeFrame.Attribute(1,"update",change),
@ -391,7 +388,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
var htmlRenderer = GetHtmlRenderer(serviceProvider); var htmlRenderer = GetHtmlRenderer(serviceProvider);
// Act // Act
var result = GetResult(Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<TestComponent>(ParameterCollection.Empty))); var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<TestComponent>(ParameterCollection.Empty)));
// Assert // Assert
Assert.Equal(expectedHtml, result); Assert.Equal(expectedHtml, result);
@ -418,7 +415,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
var htmlRenderer = GetHtmlRenderer(serviceProvider); var htmlRenderer = GetHtmlRenderer(serviceProvider);
// Act // Act
var result = GetResult(Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<TestComponent>(ParameterCollection.Empty))); var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<TestComponent>(ParameterCollection.Empty)));
// Assert // Assert
Assert.Equal(expectedHtml, result); Assert.Equal(expectedHtml, result);
@ -468,7 +465,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
var htmlRenderer = GetHtmlRenderer(serviceProvider); var htmlRenderer = GetHtmlRenderer(serviceProvider);
// Act // Act
var result = await Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<AsyncComponent>(ParameterCollection.FromDictionary(new Dictionary<string, object> var result = await htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<AsyncComponent>(ParameterCollection.FromDictionary(new Dictionary<string, object>
{ {
["Value"] = 10 ["Value"] = 10
}))); })));
@ -481,8 +478,8 @@ namespace Microsoft.AspNetCore.Components.Rendering
public async Task CanRender_NestedAsyncComponents() public async Task CanRender_NestedAsyncComponents()
{ {
// Arrange // Arrange
var dispatcher = Renderer.CreateDefaultDispatcher(); var expectedHtml = new[]
var expectedHtml = new[] { {
"<", "p", ">", "20", "</", "p", ">", "<", "p", ">", "20", "</", "p", ">",
"<", "p", ">", "80", "</", "p", ">" "<", "p", ">", "80", "</", "p", ">"
}; };
@ -492,7 +489,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
var htmlRenderer = GetHtmlRenderer(serviceProvider); var htmlRenderer = GetHtmlRenderer(serviceProvider);
// Act // Act
var result = await Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<NestedAsyncComponent>(ParameterCollection.FromDictionary(new Dictionary<string, object> var result = await htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<NestedAsyncComponent>(ParameterCollection.FromDictionary(new Dictionary<string, object>
{ {
["Nested"] = false, ["Nested"] = false,
["Value"] = 10 ["Value"] = 10

View File

@ -10,7 +10,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
{ {
protected override HtmlRenderer GetHtmlRenderer(IServiceProvider serviceProvider) protected override HtmlRenderer GetHtmlRenderer(IServiceProvider serviceProvider)
{ {
return new HtmlRenderer(serviceProvider, NullLoggerFactory.Instance, Dispatcher, _encoder); return new HtmlRenderer(serviceProvider, NullLoggerFactory.Instance, _encoder);
} }
} }
} }

View File

@ -56,14 +56,12 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
RendererRegistry rendererRegistry, RendererRegistry rendererRegistry,
RemoteRenderer renderer, RemoteRenderer renderer,
IList<ComponentDescriptor> descriptors, IList<ComponentDescriptor> descriptors,
IDispatcher dispatcher,
RemoteJSRuntime jsRuntime, RemoteJSRuntime jsRuntime,
CircuitHandler[] circuitHandlers, CircuitHandler[] circuitHandlers,
ILogger logger) ILogger logger)
{ {
CircuitId = circuitId; CircuitId = circuitId;
_scope = scope ?? throw new ArgumentNullException(nameof(scope)); _scope = scope ?? throw new ArgumentNullException(nameof(scope));
Dispatcher = dispatcher;
Client = client; Client = client;
RendererRegistry = rendererRegistry ?? throw new ArgumentNullException(nameof(rendererRegistry)); RendererRegistry = rendererRegistry ?? throw new ArgumentNullException(nameof(rendererRegistry));
Descriptors = descriptors ?? throw new ArgumentNullException(nameof(descriptors)); Descriptors = descriptors ?? throw new ArgumentNullException(nameof(descriptors));
@ -96,11 +94,9 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
public IServiceProvider Services { get; } public IServiceProvider Services { get; }
public IDispatcher Dispatcher { get; }
public Task<ComponentRenderedText> PrerenderComponentAsync(Type componentType, ParameterCollection parameters) public Task<ComponentRenderedText> PrerenderComponentAsync(Type componentType, ParameterCollection parameters)
{ {
return Dispatcher.InvokeAsync(async () => return Renderer.Dispatcher.InvokeAsync(async () =>
{ {
var result = await Renderer.RenderComponentAsync(componentType, parameters); var result = await Renderer.RenderComponentAsync(componentType, parameters);
@ -137,12 +133,12 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
// Dispatch any buffered renders we accumulated during a disconnect. // Dispatch any buffered renders we accumulated during a disconnect.
// Note that while the rendering is async, we cannot await it here. The Task returned by ProcessBufferedRenderBatches relies on // Note that while the rendering is async, we cannot await it here. The Task returned by ProcessBufferedRenderBatches relies on
// OnRenderCompleted to be invoked to complete, and SignalR does not allow concurrent hub method invocations. // OnRenderCompleted to be invoked to complete, and SignalR does not allow concurrent hub method invocations.
var _ = Renderer.InvokeAsync(() => Renderer.ProcessBufferedRenderBatches()); var _ = Renderer.Dispatcher.InvokeAsync(() => Renderer.ProcessBufferedRenderBatches());
} }
public async Task InitializeAsync(CancellationToken cancellationToken) public async Task InitializeAsync(CancellationToken cancellationToken)
{ {
await Renderer.InvokeAsync(async () => await Renderer.Dispatcher.InvokeAsync(async () =>
{ {
try try
{ {
@ -180,7 +176,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
try try
{ {
await Renderer.InvokeAsync(() => await Renderer.Dispatcher.InvokeAsync(() =>
{ {
SetCurrentCircuitHost(this); SetCurrentCircuitHost(this);
DotNetDispatcher.BeginInvoke(callId, assemblyName, methodIdentifier, dotNetObjectId, argsJson); DotNetDispatcher.BeginInvoke(callId, assemblyName, methodIdentifier, dotNetObjectId, argsJson);
@ -291,7 +287,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
{ {
Log.DisposingCircuit(_logger, CircuitId); Log.DisposingCircuit(_logger, CircuitId);
await Renderer.InvokeAsync(async () => await Renderer.Dispatcher.InvokeAsync(async () =>
{ {
try try
{ {

View File

@ -90,7 +90,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
{ {
if (DisconnectCore(circuitHost, connectionId)) if (DisconnectCore(circuitHost, connectionId))
{ {
circuitHandlerTask = circuitHost.Dispatcher.InvokeAsync(() => circuitHost.OnConnectionDownAsync(default)); circuitHandlerTask = circuitHost.Renderer.Dispatcher.InvokeAsync(() => circuitHost.OnConnectionDownAsync(default));
} }
else else
{ {
@ -189,7 +189,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
// Dispatch the circuit handlers inside the sync context to ensure the order of execution. CircuitHost executes circuit handlers inside of // Dispatch the circuit handlers inside the sync context to ensure the order of execution. CircuitHost executes circuit handlers inside of
// //
circuitHandlerTask = circuitHost.Dispatcher.InvokeAsync(async () => circuitHandlerTask = circuitHost.Renderer.Dispatcher.InvokeAsync(async () =>
{ {
if (previouslyConnected) if (previouslyConnected)
{ {

View File

@ -71,14 +71,12 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
} }
var rendererRegistry = new RendererRegistry(); var rendererRegistry = new RendererRegistry();
var dispatcher = Renderer.CreateDefaultDispatcher();
var renderer = new RemoteRenderer( var renderer = new RemoteRenderer(
scope.ServiceProvider, scope.ServiceProvider,
_loggerFactory, _loggerFactory,
rendererRegistry, rendererRegistry,
jsRuntime, jsRuntime,
client, client,
dispatcher,
encoder, encoder,
_loggerFactory.CreateLogger<RemoteRenderer>()); _loggerFactory.CreateLogger<RemoteRenderer>());
@ -93,7 +91,6 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
rendererRegistry, rendererRegistry,
renderer, renderer,
components, components,
dispatcher,
jsRuntime, jsRuntime,
circuitHandlers, circuitHandlers,
_loggerFactory.CreateLogger<CircuitHost>()); _loggerFactory.CreateLogger<CircuitHost>());

View File

@ -41,10 +41,9 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering
RendererRegistry rendererRegistry, RendererRegistry rendererRegistry,
IJSRuntime jsRuntime, IJSRuntime jsRuntime,
CircuitClientProxy client, CircuitClientProxy client,
IDispatcher dispatcher,
HtmlEncoder encoder, HtmlEncoder encoder,
ILogger logger) ILogger logger)
: base(serviceProvider, loggerFactory, dispatcher, encoder.Encode) : base(serviceProvider, loggerFactory, encoder.Encode)
{ {
_rendererRegistry = rendererRegistry; _rendererRegistry = rendererRegistry;
_jsRuntime = jsRuntime; _jsRuntime = jsRuntime;
@ -56,6 +55,8 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering
internal ConcurrentQueue<PendingRender> PendingRenderBatches = new ConcurrentQueue<PendingRender>(); internal ConcurrentQueue<PendingRender> PendingRenderBatches = new ConcurrentQueue<PendingRender>();
public override Dispatcher Dispatcher { get; } = Dispatcher.CreateDefault();
public int Id { get; } public int Id { get; }
/// <summary> /// <summary>

View File

@ -10,7 +10,6 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.Web.Rendering; using Microsoft.AspNetCore.Components.Web.Rendering;
using Microsoft.AspNetCore.Components.Rendering;
using Microsoft.AspNetCore.SignalR; using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Abstractions;
@ -27,7 +26,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
{ {
// Arrange // Arrange
var serviceScope = new Mock<IServiceScope>(); var serviceScope = new Mock<IServiceScope>();
var remoteRenderer = GetRemoteRenderer(Renderer.CreateDefaultDispatcher()); var remoteRenderer = GetRemoteRenderer();
var circuitHost = TestCircuitHost.Create( var circuitHost = TestCircuitHost.Create(
Guid.NewGuid().ToString(), Guid.NewGuid().ToString(),
serviceScope.Object, serviceScope.Object,
@ -50,7 +49,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
handler handler
.Setup(h => h.OnCircuitClosedAsync(It.IsAny<Circuit>(), It.IsAny<CancellationToken>())) .Setup(h => h.OnCircuitClosedAsync(It.IsAny<Circuit>(), It.IsAny<CancellationToken>()))
.Throws<InvalidTimeZoneException>(); .Throws<InvalidTimeZoneException>();
var remoteRenderer = GetRemoteRenderer(Renderer.CreateDefaultDispatcher()); var remoteRenderer = GetRemoteRenderer();
var circuitHost = TestCircuitHost.Create( var circuitHost = TestCircuitHost.Create(
Guid.NewGuid().ToString(), Guid.NewGuid().ToString(),
serviceScope.Object, serviceScope.Object,
@ -77,13 +76,13 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
{ {
// Arrange // Arrange
var serviceScope = new Mock<IServiceScope>(); var serviceScope = new Mock<IServiceScope>();
var remoteRenderer = GetRemoteRenderer(Renderer.CreateDefaultDispatcher()); var remoteRenderer = GetRemoteRenderer();
var circuitHost = TestCircuitHost.Create( var circuitHost = TestCircuitHost.Create(
Guid.NewGuid().ToString(), Guid.NewGuid().ToString(),
serviceScope.Object, serviceScope.Object,
remoteRenderer); remoteRenderer);
var component = new DispatcherComponent(circuitHost.Dispatcher); var component = new DispatcherComponent(circuitHost.Renderer.Dispatcher);
circuitHost.Renderer.AssignRootComponentId(component); circuitHost.Renderer.AssignRootComponentId(component);
var original = SynchronizationContext.Current; var original = SynchronizationContext.Current;
SynchronizationContext.SetSynchronizationContext(null); SynchronizationContext.SetSynchronizationContext(null);
@ -229,20 +228,19 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
handler2.VerifyAll(); handler2.VerifyAll();
} }
private static TestRemoteRenderer GetRemoteRenderer(IDispatcher dispatcher) private static TestRemoteRenderer GetRemoteRenderer()
{ {
return new TestRemoteRenderer( return new TestRemoteRenderer(
Mock.Of<IServiceProvider>(), Mock.Of<IServiceProvider>(),
new RendererRegistry(), new RendererRegistry(),
dispatcher,
Mock.Of<IJSRuntime>(), Mock.Of<IJSRuntime>(),
Mock.Of<IClientProxy>()); Mock.Of<IClientProxy>());
} }
private class TestRemoteRenderer : RemoteRenderer private class TestRemoteRenderer : RemoteRenderer
{ {
public TestRemoteRenderer(IServiceProvider serviceProvider, RendererRegistry rendererRegistry, IDispatcher dispatcher, IJSRuntime jsRuntime, IClientProxy client) public TestRemoteRenderer(IServiceProvider serviceProvider, RendererRegistry rendererRegistry, IJSRuntime jsRuntime, IClientProxy client)
: base(serviceProvider, NullLoggerFactory.Instance, rendererRegistry, jsRuntime, new CircuitClientProxy(client, "connection"), dispatcher, HtmlEncoder.Default, NullLogger.Instance) : base(serviceProvider, NullLoggerFactory.Instance, rendererRegistry, jsRuntime, new CircuitClientProxy(client, "connection"), HtmlEncoder.Default, NullLogger.Instance)
{ {
} }
@ -257,12 +255,12 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
private class DispatcherComponent : ComponentBase, IDisposable private class DispatcherComponent : ComponentBase, IDisposable
{ {
public DispatcherComponent(IDispatcher dispatcher) public DispatcherComponent(Dispatcher dispatcher)
{ {
Dispatcher = dispatcher; Dispatcher = dispatcher;
} }
public IDispatcher Dispatcher { get; } public Dispatcher Dispatcher { get; }
public bool Called { get; private set; } public bool Called { get; private set; }
public void Dispose() public void Dispose()

View File

@ -268,7 +268,6 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering
new RendererRegistry(), new RendererRegistry(),
jsRuntime.Object, jsRuntime.Object,
circuitClientProxy, circuitClientProxy,
Dispatcher,
HtmlEncoder.Default, HtmlEncoder.Default,
NullLogger.Instance); NullLogger.Instance);
} }
@ -313,7 +312,7 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering
public void TriggerRender() public void TriggerRender()
{ {
var task = _renderHandle.InvokeAsync(() => _renderHandle.Render(_renderFragment)); var task = _renderHandle.Dispatcher.InvokeAsync(() => _renderHandle.Render(_renderFragment));
Assert.True(task.IsCompletedSuccessfully); Assert.True(task.IsCompletedSuccessfully);
} }
} }
@ -342,7 +341,7 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering
public void TriggerRender() public void TriggerRender()
{ {
var task = _renderHandle.InvokeAsync(() => _renderHandle.Render(Content)); var task = _renderHandle.Dispatcher.InvokeAsync(() => _renderHandle.Render(Content));
Assert.True(task.IsCompletedSuccessfully); Assert.True(task.IsCompletedSuccessfully);
} }
} }

View File

@ -55,7 +55,7 @@ namespace Microsoft.AspNetCore.Components.Server
// Assert // Assert
AssertBinaryContents(bytes, /* startIndex */ 0, AssertBinaryContents(bytes, /* startIndex */ 0,
0, // Length of UpdatedComponents 0, // Length of UpdatedComponents
0, // Length of ReferenceFrames 0, // Length of ReferenceFrames
3, 123, int.MaxValue, int.MinValue, // DisposedComponentIds as length-prefixed array 3, 123, int.MaxValue, int.MinValue, // DisposedComponentIds as length-prefixed array
0, // Length of DisposedEventHandlerIds 0, // Length of DisposedEventHandlerIds
@ -122,7 +122,7 @@ namespace Microsoft.AspNetCore.Components.Server
2, // Length of UpdatedComponents 2, // Length of UpdatedComponents
0, // Index of UpdatedComponents[0] 0, // Index of UpdatedComponents[0]
8, // Index of UpdatedComponents[1] 8, // Index of UpdatedComponents[1]
0, // Length of ReferenceFrames 0, // Length of ReferenceFrames
0, // Length of DisposedComponentIds 0, // Length of DisposedComponentIds
0, // Length of DisposedEventHandlerIds 0, // Length of DisposedEventHandlerIds
@ -159,7 +159,7 @@ namespace Microsoft.AspNetCore.Components.Server
var bytes = Serialize(new RenderBatch( var bytes = Serialize(new RenderBatch(
new ArrayRange<RenderTreeDiff>(new[] new ArrayRange<RenderTreeDiff>(new[]
{ {
new RenderTreeDiff(123, editsSegment) new RenderTreeDiff(123, editsSegment)
}, 1), }, 1),
default, default,
default, default,
@ -360,7 +360,7 @@ namespace Microsoft.AspNetCore.Components.Server
shift += 7; shift += 7;
numBytesRead++; numBytesRead++;
} }
return result; return result;
} }
@ -376,10 +376,12 @@ namespace Microsoft.AspNetCore.Components.Server
class FakeRenderer : Renderer class FakeRenderer : Renderer
{ {
public FakeRenderer() public FakeRenderer()
: base(new ServiceCollection().BuildServiceProvider(), NullLoggerFactory.Instance, new RendererSynchronizationContext()) : base(new ServiceCollection().BuildServiceProvider(), NullLoggerFactory.Instance)
{ {
} }
public override Dispatcher Dispatcher { get; } = Dispatcher.CreateDefault();
protected override void HandleException(Exception exception) protected override void HandleException(Exception exception)
{ {
throw new NotImplementedException(); throw new NotImplementedException();

View File

@ -7,20 +7,19 @@ using System.Runtime.ExceptionServices;
using System.Text.Encodings.Web; using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.Web.Rendering; using Microsoft.AspNetCore.Components.Web.Rendering;
using Microsoft.AspNetCore.Components.Rendering;
using Microsoft.AspNetCore.SignalR; using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Abstractions;
using Moq;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Moq;
namespace Microsoft.AspNetCore.Components.Server.Circuits namespace Microsoft.AspNetCore.Components.Server.Circuits
{ {
internal class TestCircuitHost : CircuitHost internal class TestCircuitHost : CircuitHost
{ {
private TestCircuitHost(string circuitId, IServiceScope scope, CircuitClientProxy client, RendererRegistry rendererRegistry, RemoteRenderer renderer, IList<ComponentDescriptor> descriptors, IDispatcher dispatcher, RemoteJSRuntime jsRuntime, CircuitHandler[] circuitHandlers, ILogger logger) private TestCircuitHost(string circuitId, IServiceScope scope, CircuitClientProxy client, RendererRegistry rendererRegistry, RemoteRenderer renderer, IList<ComponentDescriptor> descriptors, RemoteJSRuntime jsRuntime, CircuitHandler[] circuitHandlers, ILogger logger)
: base(circuitId, scope, client, rendererRegistry, renderer, descriptors, dispatcher, jsRuntime, circuitHandlers, logger) : base(circuitId, scope, client, rendererRegistry, renderer, descriptors, jsRuntime, circuitHandlers, logger)
{ {
} }
@ -40,7 +39,6 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
clientProxy = clientProxy ?? new CircuitClientProxy(Mock.Of<IClientProxy>(), Guid.NewGuid().ToString()); clientProxy = clientProxy ?? new CircuitClientProxy(Mock.Of<IClientProxy>(), Guid.NewGuid().ToString());
var renderRegistry = new RendererRegistry(); var renderRegistry = new RendererRegistry();
var jsRuntime = new RemoteJSRuntime(Options.Create(new CircuitOptions())); var jsRuntime = new RemoteJSRuntime(Options.Create(new CircuitOptions()));
var dispatcher = Rendering.Renderer.CreateDefaultDispatcher();
if (remoteRenderer == null) if (remoteRenderer == null)
{ {
@ -50,7 +48,6 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
new RendererRegistry(), new RendererRegistry(),
jsRuntime, jsRuntime,
clientProxy, clientProxy,
dispatcher,
HtmlEncoder.Default, HtmlEncoder.Default,
NullLogger.Instance); NullLogger.Instance);
} }
@ -63,7 +60,6 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
renderRegistry, renderRegistry,
remoteRenderer, remoteRenderer,
new List<ComponentDescriptor>(), new List<ComponentDescriptor>(),
dispatcher,
jsRuntime, jsRuntime,
handlers, handlers,
NullLogger<CircuitHost>.Instance); NullLogger<CircuitHost>.Instance);

View File

@ -4,7 +4,6 @@
using System; using System;
using System.Runtime.ExceptionServices; using System.Runtime.ExceptionServices;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.RenderTree; using Microsoft.AspNetCore.Components.RenderTree;
using Xunit; using Xunit;
@ -29,7 +28,7 @@ namespace Microsoft.AspNetCore.Components.Test.Helpers
// We do it this way so that we don't have to be doing renderer.Invoke on each and every test. // We do it this way so that we don't have to be doing renderer.Invoke on each and every test.
public void TriggerRender() public void TriggerRender()
{ {
var t = _renderHandle.InvokeAsync(() => _renderHandle.Render(BuildRenderTree)); var t = _renderHandle.Dispatcher.InvokeAsync(() => _renderHandle.Render(BuildRenderTree));
// This should always be run synchronously // This should always be run synchronously
Assert.True(t.IsCompleted); Assert.True(t.IsCompleted);
if (t.IsFaulted) if (t.IsFaulted)

View File

@ -18,14 +18,18 @@ namespace Microsoft.AspNetCore.Components.Test.Helpers
{ {
} }
public TestRenderer(IDispatcher dispatcher) : base(new TestServiceProvider(), NullLoggerFactory.Instance, dispatcher) public TestRenderer(Dispatcher dispatcher) : base(new TestServiceProvider(), NullLoggerFactory.Instance)
{ {
Dispatcher = dispatcher;
} }
public TestRenderer(IServiceProvider serviceProvider) : base(serviceProvider, NullLoggerFactory.Instance, new RendererSynchronizationContext()) public TestRenderer(IServiceProvider serviceProvider) : base(serviceProvider, NullLoggerFactory.Instance)
{ {
Dispatcher = Dispatcher.CreateDefault();
} }
public override Dispatcher Dispatcher { get; }
public Action OnExceptionHandled { get; set; } public Action OnExceptionHandled { get; set; }
public Action<RenderBatch> OnUpdateDisplay { get; set; } public Action<RenderBatch> OnUpdateDisplay { get; set; }
@ -46,21 +50,21 @@ namespace Microsoft.AspNetCore.Components.Test.Helpers
public void RenderRootComponent(int componentId, ParameterCollection? parameters = default) public void RenderRootComponent(int componentId, ParameterCollection? parameters = default)
{ {
var task = InvokeAsync(() => base.RenderRootComponentAsync(componentId, parameters ?? ParameterCollection.Empty)); var task = Dispatcher.InvokeAsync(() => base.RenderRootComponentAsync(componentId, parameters ?? ParameterCollection.Empty));
UnwrapTask(task); UnwrapTask(task);
} }
public new Task RenderRootComponentAsync(int componentId) public new Task RenderRootComponentAsync(int componentId)
=> InvokeAsync(() => base.RenderRootComponentAsync(componentId)); => Dispatcher.InvokeAsync(() => base.RenderRootComponentAsync(componentId));
public new Task RenderRootComponentAsync(int componentId, ParameterCollection parameters) public new Task RenderRootComponentAsync(int componentId, ParameterCollection parameters)
=> InvokeAsync(() => base.RenderRootComponentAsync(componentId, parameters)); => Dispatcher.InvokeAsync(() => base.RenderRootComponentAsync(componentId, parameters));
public Task DispatchEventAsync(int eventHandlerId, UIEventArgs args) public Task DispatchEventAsync(int eventHandlerId, UIEventArgs args)
=> InvokeAsync(() => base.DispatchEventAsync(eventHandlerId, null, args)); => Dispatcher.InvokeAsync(() => base.DispatchEventAsync(eventHandlerId, null, args));
public new Task DispatchEventAsync(int eventHandlerId, EventFieldInfo eventFieldInfo, UIEventArgs args) public new Task DispatchEventAsync(int eventHandlerId, EventFieldInfo eventFieldInfo, UIEventArgs args)
=> InvokeAsync(() => base.DispatchEventAsync(eventHandlerId, eventFieldInfo, args)); => Dispatcher.InvokeAsync(() => base.DispatchEventAsync(eventHandlerId, eventFieldInfo, args));
private static Task UnwrapTask(Task task) private static Task UnwrapTask(Task task)
{ {

View File

@ -33,7 +33,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
appElement.FindElement(By.Id("run-without-dispatch")).Click(); appElement.FindElement(By.Id("run-without-dispatch")).Click();
Browser.Contains( Browser.Contains(
$"{typeof(InvalidOperationException).FullName}: The current thread is not associated with the renderer's synchronization context", $"{typeof(InvalidOperationException).FullName}: The current thread is not associated with the Dispatcher. Use Invoke() or InvokeAsync() to switch execution to the Dispatcher when triggering rendering or modifying any state accessed during rendering.",
() => result.Text); () => result.Text);
} }
} }

View File

@ -335,10 +335,12 @@ namespace Ignitor
class FakeRenderer : Renderer class FakeRenderer : Renderer
{ {
public FakeRenderer() public FakeRenderer()
: base(new ServiceCollection().BuildServiceProvider(), NullLoggerFactory.Instance, new RendererSynchronizationContext()) : base(new ServiceCollection().BuildServiceProvider(), NullLoggerFactory.Instance)
{ {
} }
public override Dispatcher Dispatcher { get; } = Dispatcher.CreateDefault();
protected override void HandleException(Exception exception) protected override void HandleException(Exception exception)
{ {
throw new NotImplementedException(); throw new NotImplementedException();

View File

@ -289,10 +289,12 @@ namespace Ignitor
public class FakeRenderer : Renderer public class FakeRenderer : Renderer
{ {
public FakeRenderer() public FakeRenderer()
: base(new ServiceCollection().BuildServiceProvider(), NullLoggerFactory.Instance, new RendererSynchronizationContext()) : base(new ServiceCollection().BuildServiceProvider(), NullLoggerFactory.Instance)
{ {
} }
public override Dispatcher Dispatcher { get; } = Dispatcher.CreateDefault();
protected override void HandleException(Exception exception) protected override void HandleException(Exception exception)
{ {
throw new NotImplementedException(); throw new NotImplementedException();

View File

@ -30,16 +30,14 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponents
HttpContext httpContext, HttpContext httpContext,
Type componentType) Type componentType)
{ {
var dispatcher = Renderer.CreateDefaultDispatcher();
InitializeUriHelper(httpContext); InitializeUriHelper(httpContext);
var loggerFactory = (ILoggerFactory)httpContext.RequestServices.GetService(typeof (ILoggerFactory)); var loggerFactory = (ILoggerFactory)httpContext.RequestServices.GetService(typeof (ILoggerFactory));
using (var htmlRenderer = new HtmlRenderer(httpContext.RequestServices, loggerFactory, dispatcher, _encoder.Encode)) using (var htmlRenderer = new HtmlRenderer(httpContext.RequestServices, loggerFactory, _encoder.Encode))
{ {
ComponentRenderedText result = default; ComponentRenderedText result = default;
try try
{ {
result = await dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync( result = await htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync(
componentType, componentType,
parameters)); parameters));
} }