-
Notifications
You must be signed in to change notification settings - Fork 10.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[blazor][wasm] Dispatch rendering to main thread (Net9) (#52724)
Co-authored-by: Steve Sanderson <[email protected]>
- Loading branch information
1 parent
7f312e5
commit b027cc3
Showing
30 changed files
with
824 additions
and
28 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
166 changes: 166 additions & 0 deletions
166
src/Components/WebAssembly/WebAssembly/src/Rendering/WebAssemblyDispatcher.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
namespace Microsoft.AspNetCore.Components.WebAssembly.Rendering; | ||
|
||
// When Blazor is deployed with multi-threaded runtime, WebAssemblyDispatcher will help to dispatch all Blazor JS interop calls to the main thread. | ||
// This is necessary because all JS objects have thread affinity. They are only available on the thread (WebWorker) which created them. | ||
// Also DOM is only available on the main (browser) thread. | ||
// Because all of the Dispatcher.InvokeAsync methods return Task, we don't need to propagate errors via OnUnhandledException handler | ||
internal sealed class WebAssemblyDispatcher : Dispatcher | ||
{ | ||
internal static SynchronizationContext? _mainSynchronizationContext; | ||
internal static int _mainManagedThreadId; | ||
|
||
// we really need the UI thread not just the right context, because JS objects have thread affinity | ||
public override bool CheckAccess() => _mainManagedThreadId == Environment.CurrentManagedThreadId; | ||
|
||
public override Task InvokeAsync(Action workItem) | ||
{ | ||
ArgumentNullException.ThrowIfNull(workItem); | ||
if (CheckAccess()) | ||
{ | ||
// this branch executes on correct thread and solved JavaScript objects thread affinity | ||
// but it executes out of order, if there are some pending jobs in the _mainSyncContext already, same as RendererSynchronizationContextDispatcher | ||
workItem(); | ||
// it can throw synchronously, same as RendererSynchronizationContextDispatcher | ||
return Task.CompletedTask; | ||
} | ||
|
||
var tcs = new TaskCompletionSource(); | ||
|
||
// RendererSynchronizationContext doesn't need to deal with thread affinity and so it could execute jobs on calling thread as optimization. | ||
// we could not do it for WASM/JavaScript, because we need to solve for thread affinity of JavaScript objects, so we always Post into the queue. | ||
_mainSynchronizationContext!.Post(static (object? o) => | ||
{ | ||
var state = ((TaskCompletionSource tcs, Action workItem))o!; | ||
try | ||
{ | ||
state.workItem(); | ||
state.tcs.SetResult(); | ||
} | ||
catch (Exception ex) | ||
{ | ||
state.tcs.SetException(ex); | ||
} | ||
}, (tcs, workItem)); | ||
|
||
return tcs.Task; | ||
} | ||
|
||
public override Task<TResult> InvokeAsync<TResult>(Func<TResult> workItem) | ||
{ | ||
ArgumentNullException.ThrowIfNull(workItem); | ||
if (CheckAccess()) | ||
{ | ||
// it can throw synchronously, same as RendererSynchronizationContextDispatcher | ||
return Task.FromResult(workItem()); | ||
} | ||
|
||
var tcs = new TaskCompletionSource<TResult>(); | ||
|
||
_mainSynchronizationContext!.Post(static (object? o) => | ||
{ | ||
var state = ((TaskCompletionSource<TResult> tcs, Func<TResult> workItem))o!; | ||
try | ||
{ | ||
var res = state.workItem(); | ||
state.tcs.SetResult(res); | ||
} | ||
catch (Exception ex) | ||
{ | ||
state.tcs.SetException(ex); | ||
} | ||
}, (tcs, workItem)); | ||
|
||
return tcs.Task; | ||
} | ||
|
||
public override Task InvokeAsync(Func<Task> workItem) | ||
{ | ||
ArgumentNullException.ThrowIfNull(workItem); | ||
if (CheckAccess()) | ||
{ | ||
// this branch executes on correct thread and solved JavaScript objects thread affinity | ||
// but it executes out of order, if there are some pending jobs in the _mainSyncContext already, same as RendererSynchronizationContextDispatcher | ||
return workItem(); | ||
// it can throw synchronously, same as RendererSynchronizationContextDispatcher | ||
} | ||
|
||
var tcs = new TaskCompletionSource(); | ||
|
||
_mainSynchronizationContext!.Post(static (object? o) => | ||
{ | ||
var state = ((TaskCompletionSource tcs, Func<Task> workItem))o!; | ||
|
||
try | ||
{ | ||
state.workItem().ContinueWith(t => | ||
{ | ||
if (t.IsFaulted) | ||
{ | ||
state.tcs.SetException(t.Exception); | ||
} | ||
else if (t.IsCanceled) | ||
{ | ||
state.tcs.SetCanceled(); | ||
} | ||
else | ||
{ | ||
state.tcs.SetResult(); | ||
} | ||
}, TaskScheduler.FromCurrentSynchronizationContext()); | ||
} | ||
catch (Exception ex) | ||
{ | ||
// it could happen that the workItem will throw synchronously | ||
state.tcs.SetException(ex); | ||
} | ||
}, (tcs, workItem)); | ||
|
||
return tcs.Task; | ||
} | ||
|
||
public override Task<TResult> InvokeAsync<TResult>(Func<Task<TResult>> workItem) | ||
{ | ||
ArgumentNullException.ThrowIfNull(workItem); | ||
if (CheckAccess()) | ||
{ | ||
// this branch executes on correct thread and solved JavaScript objects thread affinity | ||
// but it executes out of order, if there are some pending jobs in the _mainSyncContext already, same as RendererSynchronizationContextDispatcher | ||
return workItem(); | ||
// it can throw synchronously, same as RendererSynchronizationContextDispatcher | ||
} | ||
|
||
var tcs = new TaskCompletionSource<TResult>(); | ||
|
||
_mainSynchronizationContext!.Post(static (object? o) => | ||
{ | ||
var state = ((TaskCompletionSource<TResult> tcs, Func<Task<TResult>> workItem))o!; | ||
try | ||
{ | ||
state.workItem().ContinueWith(t => | ||
{ | ||
if (t.IsFaulted) | ||
{ | ||
state.tcs.SetException(t.Exception); | ||
} | ||
else if (t.IsCanceled) | ||
{ | ||
state.tcs.SetCanceled(); | ||
} | ||
else | ||
{ | ||
state.tcs.SetResult(t.Result); | ||
} | ||
}, TaskScheduler.FromCurrentSynchronizationContext()); | ||
} | ||
catch (Exception ex) | ||
{ | ||
state.tcs.SetException(ex); | ||
} | ||
}, (tcs, workItem)); | ||
|
||
return tcs.Task; | ||
} | ||
} |
Oops, something went wrong.