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
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
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
ManualResetEvent and other synchronization primitives are working as intended between threads.
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
This can be done in the project file like this:
After that, creating a thread becomes possible (view the full project here):
Which produces the following output:
[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.
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.
I’ll continue the story on how enable threading in the
CoreDispatcher.RunAsync() in the Uno Platform.