Skip to content

Blazor API Review: Design concept for Dispatcher #11930

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jul 12, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ namespace Microsoft.AspNetCore.Blazor.Rendering
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 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<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; }
Expand Down
61 changes: 61 additions & 0 deletions src/Components/Blazor/Blazor/src/Rendering/NullDispatcher.cs
Original file line number Diff line number Diff line change
@@ -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();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ public WebAssemblyRenderer(IServiceProvider serviceProvider, ILoggerFactory logg
_webAssemblyRendererId = RendererRegistry.Current.Add(this);
}

public override Dispatcher Dispatcher => NullDispatcher.Instance;

/// <summary>
/// Attaches a new root component to the renderer,
/// causing it to be displayed in the specified DOM element.
Expand Down
6 changes: 4 additions & 2 deletions src/Components/Blazor/Build/test/RazorIntegrationTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ protected RenderTreeFrame[] GetRenderTree(IComponent component)
protected private RenderTreeFrame[] GetRenderTree(TestRenderer renderer, IComponent 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.
Assert.True(task.Status.HasFlag(TaskStatus.RanToCompletion) || task.Status.HasFlag(TaskStatus.Faulted));
if (task.IsFaulted)
Expand Down Expand Up @@ -442,10 +442,12 @@ protected class CompileToAssemblyResult

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 void AttachComponent(IComponent component)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,7 @@ public async Task SupportsTwoWayBindingForTextboxes()
// Trigger the change event to show it updates the property
//
// 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);
await task;

Expand Down Expand Up @@ -367,7 +367,7 @@ public async Task SupportsTwoWayBindingForTextareas()
// Trigger the change event to show it updates the property
//
// 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);
await task;

Expand Down Expand Up @@ -404,7 +404,7 @@ public async Task SupportsTwoWayBindingForDateValues()
//
// This should always complete synchronously.
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);
await task;

Expand Down Expand Up @@ -440,7 +440,7 @@ public async Task SupportsTwoWayBindingForDateValuesWithFormatString()
// Trigger the change event to show it updates the property
//
// 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);
await task;

Expand Down Expand Up @@ -559,7 +559,7 @@ public async Task SupportsTwoWayBindingForBoolValues()
// Trigger the change event to show it updates the property
//
// 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);
await task;

Expand Down Expand Up @@ -595,7 +595,7 @@ public async Task SupportsTwoWayBindingForEnumValues()
// Trigger the change event to show it updates the property
//
// 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);
await task;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,12 @@ public void ComputeDiff_SingleFormField()
private class FakeRenderer : Renderer
{
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)
{
throw new NotImplementedException();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,17 @@ public DataTransfer() { }
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 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)]
public readonly partial struct ElementRef
{
Expand Down Expand Up @@ -303,13 +314,6 @@ public partial interface IComponentContext
{
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
{
System.Threading.Tasks.Task OnAfterRenderAsync();
Expand Down Expand Up @@ -406,9 +410,8 @@ public readonly partial struct RenderHandle
{
private readonly object _dummy;
private readonly int _dummyPrimitive;
public Microsoft.AspNetCore.Components.Dispatcher Dispatcher { 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) { }
}
[System.AttributeUsageAttribute(System.AttributeTargets.Class, AllowMultiple=true, Inherited=false)]
Expand Down Expand Up @@ -654,7 +657,8 @@ public EventFieldInfo() { }
}
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) { }
[System.Diagnostics.DebuggerStepThroughAttribute]
public System.Threading.Tasks.Task<Microsoft.AspNetCore.Components.Rendering.ComponentRenderedText> RenderComponentAsync(System.Type componentType, Microsoft.AspNetCore.Components.ParameterCollection initialParameters) { throw null; }
Expand All @@ -673,18 +677,15 @@ public readonly partial struct RenderBatch
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, Microsoft.AspNetCore.Components.IDispatcher dispatcher) { }
public abstract Microsoft.AspNetCore.Components.Dispatcher Dispatcher { get; }
public event System.UnhandledExceptionEventHandler UnhandledSynchronizationException { add { } remove { } }
protected internal virtual void AddToRenderQueue(int componentId, Microsoft.AspNetCore.Components.RenderFragment renderFragment) { }
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 void Dispose() { }
protected virtual void Dispose(bool disposing) { }
protected abstract void HandleException(System.Exception exception);
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; }
[System.Diagnostics.DebuggerStepThroughAttribute]
protected System.Threading.Tasks.Task RenderRootComponentAsync(int componentId, Microsoft.AspNetCore.Components.ParameterCollection initialParameters) { throw null; }
Expand Down
4 changes: 2 additions & 2 deletions src/Components/Components/src/ComponentBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -148,15 +148,15 @@ protected virtual Task OnAfterRenderAsync()
/// </summary>
/// <param name="workItem">The work item to execute.</param>
protected Task InvokeAsync(Action workItem)
=> _renderHandle.InvokeAsync(workItem);
=> _renderHandle.Dispatcher.InvokeAsync(workItem);

/// <summary>
/// Executes the supplied work item on the associated renderer's
/// synchronization context.
/// </summary>
/// <param name="workItem">The work item to execute.</param>
protected Task InvokeAsync(Func<Task> workItem)
=> _renderHandle.InvokeAsync(workItem);
=> _renderHandle.Dispatcher.InvokeAsync(workItem);

void IComponent.Configure(RenderHandle renderHandle)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,34 +10,66 @@ namespace Microsoft.AspNetCore.Components
/// <summary>
/// Dispatches external actions to be executed on the context of a <see cref="Renderer"/>.
/// </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>
/// Invokes the given <see cref="Action"/> in the context of the associated <see cref="Renderer"/>.
/// </summary>
/// <param name="workItem">The action to execute.</param>
/// <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>
/// Invokes the given <see cref="Func{TResult}"/> in the context of the associated <see cref="Renderer"/>.
/// </summary>
/// <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>
Task InvokeAsync(Func<Task> workItem);
public abstract Task InvokeAsync(Func<Task> workItem);

/// <summary>
/// Invokes the given <see cref="Func{TResult}"/> in the context of the associated <see cref="Renderer"/>.
/// </summary>
/// <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>
Task<TResult> InvokeAsync<TResult>(Func<TResult> workItem);
public abstract Task<TResult> InvokeAsync<TResult>(Func<TResult> workItem);

/// <summary>
/// Invokes the given <see cref="Func{TResult}"/> in the context of the associated <see cref="Renderer"/>.
/// </summary>
/// <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>
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);
}
}
}
Loading