|
|
@@ -52,7 +52,7 @@ namespace Microsoft.AspNetCore.Components.Browser.Rendering
|
|
|
_rendererRegistry = rendererRegistry;
|
|
|
_jsRuntime = jsRuntime;
|
|
|
_client = client;
|
|
|
- _syncContext = syncContext;
|
|
|
+ _syncContext = syncContext ?? throw new ArgumentNullException(nameof(syncContext));
|
|
|
|
|
|
_id = _rendererRegistry.Add(this);
|
|
|
}
|
|
|
@@ -90,6 +90,36 @@ namespace Microsoft.AspNetCore.Components.Browser.Rendering
|
|
|
RenderRootComponent(componentId);
|
|
|
}
|
|
|
|
|
|
+ /// <inheritdoc />
|
|
|
+ public override Task Invoke(Action workItem)
|
|
|
+ {
|
|
|
+ if (SynchronizationContext.Current == _syncContext)
|
|
|
+ {
|
|
|
+ // No need to dispatch. Avoid deadlock by invoking directly.
|
|
|
+ return base.Invoke(workItem);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ var syncContext = (CircuitSynchronizationContext)_syncContext;
|
|
|
+ return syncContext.Invoke(workItem);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <inheritdoc />
|
|
|
+ public override Task InvokeAsync(Func<Task> workItem)
|
|
|
+ {
|
|
|
+ if (SynchronizationContext.Current == _syncContext)
|
|
|
+ {
|
|
|
+ // No need to dispatch. Avoid deadlock by invoking directly.
|
|
|
+ return base.InvokeAsync(workItem);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ var syncContext = (CircuitSynchronizationContext)_syncContext;
|
|
|
+ return syncContext.InvokeAsync(workItem);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
/// <summary>
|
|
|
/// Disposes the instance.
|
|
|
/// </summary>
|
|
|
@@ -101,14 +131,18 @@ namespace Microsoft.AspNetCore.Components.Browser.Rendering
|
|
|
protected override void AddToRenderQueue(int componentId, RenderFragment renderFragment)
|
|
|
{
|
|
|
// Render operations are not thread-safe, so they need to be serialized.
|
|
|
- // This also ensures that when the renderer invokes component lifecycle
|
|
|
- // methods, it does so on the expected sync context.
|
|
|
- // We have to use "Post" (for async execution) because if it blocked, it
|
|
|
- // could deadlock when a child triggers a parent re-render.
|
|
|
- _syncContext.Post(_ =>
|
|
|
+ // Plus, any other logic that mutates state accessed during rendering also
|
|
|
+ // needs not to run concurrently with rendering so should be dispatched to
|
|
|
+ // the renderer's sync context.
|
|
|
+ if (SynchronizationContext.Current != _syncContext)
|
|
|
{
|
|
|
- base.AddToRenderQueue(componentId, renderFragment);
|
|
|
- }, null);
|
|
|
+ throw new RemoteRendererException(
|
|
|
+ "The current thread is not associated with the renderer's synchronization context. " +
|
|
|
+ "Use Invoke() or InvokeAsync() to switch execution to the renderer's synchronization " +
|
|
|
+ "context when triggering rendering or modifying any state accessed during rendering.");
|
|
|
+ }
|
|
|
+
|
|
|
+ base.AddToRenderQueue(componentId, renderFragment);
|
|
|
}
|
|
|
|
|
|
/// <inheritdoc />
|