| File: Rendering\WebAssemblyDispatcher.cs | Web Access |
| Project: src\aspnetcore\src\Components\WebAssembly\WebAssembly\src\Microsoft.AspNetCore.Components.WebAssembly.csproj (Microsoft.AspNetCore.Components.WebAssembly) |
// 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; } }