Skip to content

Commit 51c7008

Browse files
committed
WebAssemblyDispatcher
1 parent b640052 commit 51c7008

File tree

3 files changed

+142
-1
lines changed

3 files changed

+142
-1
lines changed

src/Components/Components/src/Rendering/RendererSynchronizationContextDispatcher.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ public RendererSynchronizationContextDispatcher()
2020

2121
public override Task InvokeAsync(Action workItem)
2222
{
23+
ArgumentNullException.ThrowIfNull(workItem);
2324
if (CheckAccess())
2425
{
2526
workItem();
@@ -31,6 +32,7 @@ public override Task InvokeAsync(Action workItem)
3132

3233
public override Task InvokeAsync(Func<Task> workItem)
3334
{
35+
ArgumentNullException.ThrowIfNull(workItem);
3436
if (CheckAccess())
3537
{
3638
return workItem();
@@ -41,6 +43,7 @@ public override Task InvokeAsync(Func<Task> workItem)
4143

4244
public override Task<TResult> InvokeAsync<TResult>(Func<TResult> workItem)
4345
{
46+
ArgumentNullException.ThrowIfNull(workItem);
4447
if (CheckAccess())
4548
{
4649
return Task.FromResult(workItem());
@@ -51,6 +54,7 @@ public override Task<TResult> InvokeAsync<TResult>(Func<TResult> workItem)
5154

5255
public override Task<TResult> InvokeAsync<TResult>(Func<Task<TResult>> workItem)
5356
{
57+
ArgumentNullException.ThrowIfNull(workItem);
5458
if (CheckAccess())
5559
{
5660
return workItem();
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Microsoft.AspNetCore.Components.WebAssembly.Rendering;
5+
6+
internal sealed class WebAssemblyDispatcher : Dispatcher
7+
{
8+
public static readonly Dispatcher Instance = new WebAssemblyDispatcher();
9+
private readonly SynchronizationContext? _context;
10+
11+
private WebAssemblyDispatcher()
12+
{
13+
// capture the JSSynchronizationContext from the main thread.
14+
// if SynchronizationContext.Current is null on main thread, we are in single-threaded flavor of the dotnet wasm runtime
15+
_context = SynchronizationContext.Current;
16+
}
17+
18+
public override bool CheckAccess() => SynchronizationContext.Current == _context || _context == null;
19+
20+
public override Task InvokeAsync(Action workItem)
21+
{
22+
ArgumentNullException.ThrowIfNull(workItem);
23+
if (CheckAccess())
24+
{
25+
workItem();
26+
return Task.CompletedTask;
27+
}
28+
29+
_context!.InvokeAsync(workItem);
30+
return Task.CompletedTask;
31+
}
32+
33+
public override Task InvokeAsync(Func<Task> workItem)
34+
{
35+
ArgumentNullException.ThrowIfNull(workItem);
36+
if (CheckAccess())
37+
{
38+
return workItem();
39+
}
40+
41+
return _context!.InvokeAsync(workItem);
42+
}
43+
44+
public override Task<TResult> InvokeAsync<TResult>(Func<TResult> workItem)
45+
{
46+
ArgumentNullException.ThrowIfNull(workItem);
47+
if (CheckAccess())
48+
{
49+
return Task.FromResult(workItem());
50+
}
51+
52+
return _context!.InvokeAsync(static (workItem) => Task.FromResult(workItem()), workItem);
53+
}
54+
55+
public override Task<TResult> InvokeAsync<TResult>(Func<Task<TResult>> workItem)
56+
{
57+
ArgumentNullException.ThrowIfNull(workItem);
58+
if (CheckAccess())
59+
{
60+
return workItem();
61+
}
62+
63+
return _context!.InvokeAsync(workItem);
64+
}
65+
}
66+
67+
internal static class SynchronizationContextExtension
68+
{
69+
public static void InvokeAsync(this SynchronizationContext self, Action body)
70+
{
71+
Exception? exc = default;
72+
self.Send((_) =>
73+
{
74+
try
75+
{
76+
body();
77+
}
78+
catch (Exception ex)
79+
{
80+
exc = ex;
81+
}
82+
}, null);
83+
if (exc != null)
84+
{
85+
throw exc;
86+
}
87+
}
88+
89+
public static TRes InvokeAsync<TRes>(this SynchronizationContext self, Func<TRes> body)
90+
{
91+
TRes? value = default;
92+
Exception? exc = default;
93+
self.Send((_) =>
94+
{
95+
try
96+
{
97+
value = body();
98+
}
99+
catch (Exception ex)
100+
{
101+
exc = ex;
102+
}
103+
}, null);
104+
if (exc != null)
105+
{
106+
throw exc;
107+
}
108+
return value!;
109+
}
110+
111+
public static TRes InvokeAsync<T1, TRes>(this SynchronizationContext self, Func<T1, TRes> body, T1 p1)
112+
{
113+
TRes? value = default;
114+
Exception? exc = default;
115+
self.Send((_) =>
116+
{
117+
try
118+
{
119+
value = body(p1);
120+
}
121+
catch (Exception ex)
122+
{
123+
exc = ex;
124+
}
125+
}, null);
126+
if (exc != null)
127+
{
128+
throw exc;
129+
}
130+
return value!;
131+
}
132+
}

src/Components/WebAssembly/WebAssembly/src/Rendering/WebAssemblyRenderer.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,21 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Rendering;
2222
internal sealed partial class WebAssemblyRenderer : WebRenderer
2323
{
2424
private readonly ILogger _logger;
25+
private readonly Dispatcher _dispatcher;
2526

2627
public WebAssemblyRenderer(IServiceProvider serviceProvider, ILoggerFactory loggerFactory, JSComponentInterop jsComponentInterop)
2728
: base(serviceProvider, loggerFactory, DefaultWebAssemblyJSRuntime.Instance.ReadJsonSerializerOptions(), jsComponentInterop)
2829
{
2930
_logger = loggerFactory.CreateLogger<WebAssemblyRenderer>();
31+
// if SynchronizationContext.Current is null on main thread, we are in single-threaded flavor of the dotnet wasm runtime
32+
_dispatcher = SynchronizationContext.Current==null
33+
? NullDispatcher.Instance
34+
: WebAssemblyDispatcher.Instance
3035

3136
ElementReferenceContext = DefaultWebAssemblyJSRuntime.Instance.ElementReferenceContext;
3237
}
3338

34-
public override Dispatcher Dispatcher => NullDispatcher.Instance;
39+
public override Dispatcher Dispatcher => _dispatcher;
3540

3641
public Task AddComponentAsync([DynamicallyAccessedMembers(Component)] Type componentType, ParameterView parameters, string domElementSelector)
3742
{

0 commit comments

Comments
 (0)