Threading, in general operating systems sense, is not something that the web has been able to use until very recently. The addition of Threads support in WebAssembly, and the activation of the threading support in Chrome opens up a whole new world of possibilities, including the use of Reactive Extensions (Rx.NET) or the Task Parallel Library (TPL).

Let’s dive in, with some sample code.

A bit of history

The only technique that was available for actual local parallelization of work in the javascript land was to use WebWorkers. It’s technically not threading in the same sense known by out-of-browsers developers, as it does not provide the ability to share memory between WebWorkers. To synchronize work, messages are passed between workers and the main loop, which looks more like processes would exchange messages via IPC.

This changed recently with the ability for javascript to create shared array buffers, contiguous pieces of memory that can be both read and written from workers and the main loop. Those buffers can only be mapped to primitive types, for which access and manipulated can be synchronized via atomic operations. Atomics are also used to perform signalling between threads.

Those buffers had been disabled for a while because of CPU attacks Spectre and Meltdown, but are now enabled by default in Chrome and the new Edge.

Threads in Emscripten

WebAssembly Threads are supported in Emscripten via the pthreads library and are backed by WebWorkers.

When threads are created, new WebWorkers are created and provided with a set of information, such as stack size, thread ID, shared memory, etc… The same WebAssembly module as the main loop one is loaded in memory in the worker, and executes the entry point requested for the thread.

One interesting aspect of threading is that the creation of WebWorkers needs main loop to yield. This means that if the main loop does not yield control back to the environment, threads may never get the chance to start. That’s why Emscripten provides a way set of workers (none by default) to be created before executing any code.

At this point, it is important to note that if the atomics feature is not enabled in the browser (e.g. in Firefox or Safari), the emscripten built app will fail to start. This will most probably one of the reason that the Uno.Wasm.Bootstrap project will include multi-configuration generation based on browsers capabilities.

Threads in .NET for WebAssembly

.NET for WebAssembly now supports the ability to create threads, as the runtime (Mono) uses pthreads. All the existing internal .NET threading APIs have been enabled to make use of pthreads as they do for other platforms, and the System.Threading becomes available for use.

With this it becomes possible to use Monitor (with lock() statements), AutoResetEvent and ManualResetEvent and other synchronization primitives are working as intended between threads.

The ThreadPool is also available, along with System.Threading.Thread.ManagedThreadId, thread names and thread local storage (TLS).

Trying out WebAssembly Threading with Uno.Wasm.Bootstrap

The Uno.Wasm.Bootstrap package provides the configuration to enable threading in .NET for WebAssembly by using the latest 1.1-dev package, and changing the active runtime mode.

This can be done in the project file like this:

<MonoWasmRuntimeConfiguration>threads-release</MonoWasmRuntimeConfiguration>

After that, creating a thread becomes possible (view the full project here):

Which produces the following output:

[tid:1] Startup
[tid:1] Waiting for completion source
[tid:2] Thread begin
[tid:2] Waiting for event
[tid:1] Got task result, raising event
[tid:1] Main thread exiting
[tid:2] Got event, terminating thread

The execution is now interleaved properly, as expected when running this sample in common .NET environment.

This sample is build to work with no available upfront WebWorkers, which is why the Run method is invoked as Fire and Forget, so that the main loop can get the chance to start the workers.

The bootstrapper configuration does not yet provide a way to change the preexisting workers, but will soon get it.

Threading affinity

API thread affinity is a tricky subject. Most browser APIs can only be invoked from the main loop, such as DOM manipulation.

Emscripten provides a feature called “Proxying” which detects APIs need to be invoked on the main loop. The user code is then rewriten to create a blocking call in the worker until the method execution finishes on the other thread. This execution is done through WebWorkers message passing, but this is not something that we’ll be able to use in .NET, as IL does not use the proper APIs to executed proxied code.

Along with those limitations are the inability for the main loop to be blocked. Emscripten provides a way to seemingly block the main thread through spinlocks, though that’s generally not a good idea for the end user perceived performance.

Coming next…

I’ll continue the story on how enable threading in the CoreDispatcher.RunAsync() in the Uno Platform.