-
Couldn't load subscription status.
- Fork 5.2k
Description
Update this is no longer a public API proposal. instead, we should make an internal SynchronizationContext subclass that is installed by the runtime on the main thread.
Background and motivation
In order to enable multi-threaded WebAssembly programming, we want to allow users to continue to program using async/await. In the current single-threaded browser-wasm runtime, all asynchronous tasks run on the main browser thread implicitly as there is nowhere else to run them. Our multi-threaded WebAssembly programming model is built on WebWorkers - dedicated threads that do not share system memory with the main browser thread but may pass messages to it back and forth including WebAssembly main memory using a SharedArrayBuffer.
In multi-threaded WebAssembly, certain operations (for example code that manipulates the DOM of the current page) must run on the browser's main event loop, not in a WebWorker. As a result, it is necessary to add a SynchronizationContext subclass that allows asynchronous work to be posted back to the main browser thread.
API Proposal
using System.Threading;
namespace System.Runtime.InteropServices.JavaScript;
/// A synchronization context that queues work to run on the browser's main thread.
/// In a multi-threaded web application certain operations (such as manipulating the DOM) must be done on the
/// main thread and not in a WebWorker. The BrowserSynchronizationContext allows asynchronous callbacks to
/// return to run their continuations on the main thread.
[SupportedOSPlatform("browser")]
internal sealed class BrowserSynchronizationContext : SynchronizationContext
{
internal BrowserSynchronizationContext();
public override SynchronizatonContext CreateCopy();
public override void Post(SendOrPostCallback d, object state);
//// Always throws NotSupportedException
public override void Send(SendOrPostCallback d, object state);
}API Usage
In a .razor page in Blazor:
<button @onclick="DoSomeWork">Start Work</button>
<text>Result is: @output</text>
@code {
string output = "";
public async Task DoSomeWork() {
output = "pending";
Debug.Assert (SynchronizationContext.Current instanceof BrowserSynchronizationContext);
var result = await Task.Run(Model.SomeAsyncWork); // start some work on the threadpool
output = result.ToString(); // return to the UI thread and update the model
}
}Alternative Designs
- We could create some kind of JavaScript shim that exposes proxy objects for manipulating the DOM from an arbitrary webworker and leave the C# layer oblivious. (We would have to do this for every JS API that is only available in the main browser thread)
- We could add a synchronization context class for posting work to arbitrary web workers' event loops. In that case the main thread would not be special:
public class JSEventLoopSynchronizationContext : SynchronizationContext { public JSEventLoopSynchronizationContext (Thread t); public static JSEventLoopSynchronizationContext Browser; // same overrides as before }
We can also consider some other names:
- some other namespace
DOMSynchronizationContextMainEventLoopSynchronizationContextJavaScriptSynchronizationContext
Other considerations:
- the eventloop, tasks and microtasks are not a browser concept, they're a JS concept. So we may want a name that makes sense in Node environments, too.
Risks
No response