Description
Description
The other day we started looking into what it'd take to enable multi threading for our wasmbrowser project that we utilize along an angular app, with the main purpose of utilizing a bunch of business logic within the browser written in C#.
We started the WASM component in a web-worker to off-load it from the "main" thread to avoid it looking UI when processing things. However, now with MT it does not seem to work - as it fails on startup during:
mono_wasm_pthread_on_pthread_attached-> currentWorkerThreadEvents.dispatchEvent(makeWorkerThreadEvent(dotnetPthreadAttached, pthread_self));
From what I have gathered it tries to do "dispatchEvent" on the "UI" thread which is also a worker.
This might not be a bug, but expected to not work - that the new MT design is meant to always be started in the web context and not in a worker context.
Reproduction Steps
wasmbrowser project worker.js
import { dotnet } from "./_framework/dotnet.js";
let modulePromise = null;
function loadWasm() {
console.log("SharedArrayBuffer available:", typeof SharedArrayBuffer !== 'undefined', globalThis);
console.info("Loading WASM module...");
return dotnet
.withConfig({
// cacheBootResources: true // currently not supported: https://github.com/dotnet/runtime/issues/97787
})
.create()
.then((net) => {
const { getAssemblyExports, getConfig } = net;
const config = getConfig();
return getAssemblyExports(config.mainAssemblyName);
})
.then((module) => {
console.info("WASM module loaded...");
return module;
});
}
function ensureLoaded() {
return !modulePromise
? (modulePromise = loadWasm())
: modulePromise;
}
function test() {
return ensureLoaded().then((module) =>
module.WasmClient.Test()
);
}
// dummied down for example
addEventListener("message", (event) => {
if (event.data.method == "test") {
test().then((output) => {
postMessage({ result: output });
});
}
});
wasmbrowser C# sample code
using System.Runtime.InteropServices.JavaScript;
using System.Threading.Tasks;
return; // Main entry point do not remove.
public partial class WasmClient
{
[JSExport]
public static Task<string> Test()
{
return Task.FromResult("Hello from C#");
}
}
Then from JS side depending how you configure it
var worker = new Worker('./assets/wasm/worker.js', { type: 'module' });
worker.addEventListener('message', (event) => {
console.log(event); // log output from wasm
});
worker.postMessage({
method: "test"
});
Feel free to ping me if I should setup a repo project for simplification.
Expected behavior
Expected behaviour is that the wasm project loads inside of the worker, the method is called and returns the Hello from C#
. Note: This works just fine if you turn off multithreading.
Actual behavior
Regression?
Not that we know of - might be an untested territory, worth to note - this works just fine without "MT" enabled.
Known Workarounds
No response
Configuration
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<RuntimeIdentifier>browser-wasm</RuntimeIdentifier>
<OutputType>Exe</OutputType>
<WasmMainJSPath>main.js</WasmMainJSPath><!-- points towards an empty file with console log -->
<WasmEnableSIMD>true</WasmEnableSIMD>
<WasmEnableThreads>true</WasmEnableThreads>
<TrimMode>partial</TrimMode>
<PublishTrimmed>true</PublishTrimmed>
<WasmBuildNative>true</WasmBuildNative>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<PublishWithAot>false</PublishWithAot>
<WasmStripILAfterAOT>false</WasmStripILAfterAOT>
<WasmAppDir>../webapp/src/assets/wasm</WasmAppDir><!-- adjust accordingly -->
</PropertyGroup>
Build project with dotnet publish
and the files needed will be copied if WasmAppDir
is configured correctly.
Other information
mono_wasm_pthread_on_pthread_attached being called for the "UI" aka main thread even though the comment says it should not. But when the main thread is a worker it fails some pre-check (is the initial guess)