Skip to content

[wasm-mt] Add a System.Runtime.InteropServices.JavaScript.BrowserSynchronizationContext #69409

@lambdageek

Description

@lambdageek

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

  1. 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)
  2. 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
  • DOMSynchronizationContext
  • MainEventLoopSynchronizationContext
  • JavaScriptSynchronizationContext

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions