-
Notifications
You must be signed in to change notification settings - Fork 4.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[browser][MT] Multithreading and JavaScript async interop in .NET 9 #85592
Comments
/cc @lewing @pavelsavara |
Tagging subscribers to 'arch-wasm': @lewing Issue DetailsTracking issue for further work on JS interop and multithreading. Constituent part of #76956 Scenarios
Work Items[blazor]
[pool-promise]
[pool-timeout]
[thread-eventloop]
|
Blazor's requirements are:
|
I'm using the latest .net8.0 RC2 release in a multithreaded WASM app using UNO and Skiasharp. I am getting the "Please use dedicated worker...etc" from the AssertWebWorkerContext() method as expected. The work around is sending JSInterop calls to the main thread, which works but results in some animation stuttering particularly with large db/cryptography jobs. I would like to try out the WebWorker class but it does not appear to be included in the Microsoft.NETCore.App\8.0.0-rc.2.23479.6\System.Runtime.InteropServices.JavaScript.dll which the application is using and because of the internal methods I see how to implement it outside of the dotnet runtime. Is this just not available in released packages yet or is there some way to enable it? I notice there is a sample that uses it but doesn't this need compiling the runtime from source? |
@Rippletank Threads are not supported in .Net 8. As you can see above, there are many reasons why we are not ready for it. |
@pavelsavara Thanks for the update. I'm curious if we'll ever have this, because if I remember correctly, this was .net7 --> .net8, and now it's .net8 --> .net9 |
Ok, I see. It is confusing because multithreading is enabled with Uno web assembly apps and it works within the app itself but obviously doesn't work with the interop. I'm curious, is ok to use it, apart from interop issues, or is it generally problematic at the moment? For things like http etc. |
MT is problematic in Net8 in my experience, that's why it's experimental. I suggest you ask Uno how to turn it of.
Yes those are not easy problems. We are not out of the woods yet for Net9 either. We don't want to ship product which could randomly deadlock. Wish us luck. |
Here is our use-case: We have a Client-side only Blazor WASM app. Our main component is a PCB 3D Board Viewer. We load the data from our database but the data itself is not that complex (source from CAD formats). It does not technically contain the 3D data we need to render. So we build the 3D models on demand, based on our own Data model which is being deserialized and interpreted, all client side. The amount of details on such a board is immense, meaning we will have to do A LOT of calculations. For big boards we may have waiting times for more than 4 minutes, simply waiting for the deserialization (not too bad) and generation of 3D vertices, uvs and so on. During this time, the browser is pretty much completely frozen, we have some Task.Delay(1)'s here and there to update a progress bar in between but often we get the "not responding" message anyway. This is obviously a horrible user experience. Our dream is to be able to offload this generation to separate threads, for example separate board layers don't depend on each other and can be built completely in parallel. Ideally we want to show something really quickly, and then in the background keep calculating stuff that become available once done. This is obviously nothing but a dream at this point, still being restricted to the UI thread (and potentially web workers which we will look into). The whole thing does not get any better from the fact that we depend on A LOT of JS interop too... But that is another story. So that is our use-case. One could ask why we don't do server-side rendering, but the fact is that we don't really have a deployed production "web" version yet. Currently we are providing this as an extension of our legacy phat client (embedding the SPA in a CEF Sharp window). One of our selling points is also the fact that we "make everything happen" in the browser, without the need for a backend. We also offer a "from file" option which keeps all data in the browser, meaning we definately have to do the calculations in the wasm app. |
Copying my use case from #68162: I have an app I'm trying to port, which calls a blocking function in a non-managed dll:
To port it to WASM/JS it instead needs to call an async JS function (ignore that it no longer takes any arguments):
I've made an interface that lets me link to either the DLL or the JSImport, but I just need some way to block while I wait for the promise to resolve. And that's pretty much it! I don't really care how this is accomplished. While the details of which threads run where will matter for other people and their use cases, for me it's just an implementation detail. An elegant solution is more what I'm after. (I also tried making the app async, but it's a large messy legacy app (28k+ LOC) and I would end up needing convert nearly everything to async functions, so threads seems a better option.) (And async functions not having |
Copy-pasting use-cases from here: https://www.reddit.com/r/Blazor/comments/199o6e6/why_is_multithreading_such_a_requested_feature/
|
@pavelsavara those are not my words. That's just a list of use-cases I came across yesterday. Btw, someone else in that Reddit thread asked a similar question. |
We will also benefit from this enhancement at DevExpress in certain products, and our customers will for sure benefit in their projects (while migrating shared code to .NET 8 and Blazor). For instance, in our application framework DevExpress XAF (powered by ASP.NET Core Blazor), we researched ways to plug in a middle tier service into our existing code base with Blazor WebAssembly and EF Core. The following code throws "Cannot wait on monitors on this runtime" with Blazor .NET 8 as it did 5 years ago when we started supporting Blazor in XAF and experimented with WebAssembly back then (dropped WebAssembly due to low performance and the lack of multi-threading support). Today we still rely on Blazor Server in XAF Blazor, but want to support WebAssembly render modes in the future.
NOTE: our Blazor UI Components already fully support both WebAssembly and Server modes from November 2023. As other readers also noted, we also want to reuse a large amount of our existing framework core code in Blazor WebAssembly, as we successfully did in Blazor Server, WinForms and WebForms for over 15 years now. Converting "nearly everything to async functions" in the framework core code is NOT realistic due to the huge breaking changes for other customers and rewrite costs, of course (it may be easier to drop everything and start over with a new product then). Thank you for your consideration. |
We are still re-considering this one. See my other comment.
We understand that ^^. Alternatives are
|
The mono runtime is compiled using Emscripten. Is this also the case with AOT compilation? If that's the case, any chance that we could run ASYNCIFY like we would for any other Emscripten app? That could be another way of converting sync functions into async. |
Trying .net9 preview 2, wasmbrowser template, the all synchronous call .net->js->.net hangs the browser window. Seems like a regression from .net8 |
This is by design on multi-threaded build. The reason is that managed code is not running on UI thread, but your JS is running there. The managed thread is blocked waiting for the first call to return from JS while you try to send it another message with managed call. Nested synchronous JS interop calls would not be supported in MT at all. It will throw PNSE in next previews. Here is work in progress PR on the topic. #99833 Single-threaded build would not change in this regard. |
@pavelsavara would it be possible to call a sync .NET exported function (or a JSImport callback) without blocking JS thread? I.e. discarding a result. Making .NET function async is a decent workaround here, but I don't see why it should be forced for "fire and forget" scenarios. Thanks. |
Also, will new behavior be toggleable? It's not yet clear if we can support new threading model in .NET 9 WASM, since it has to work with sync requestAnimationFrame callbacks, webGL and Skia on top of Emscripten, something that yet needs to be tested. Or even better, could it be possibly to initialize .NET thread on a physical UI thread? In our case it would work as a render thread with Skia and WebGL. While the rest of the app is managed on a normal deputy thread. |
I believe that the lack of access to the "true" UI thread would completely break WebGL interop. Another problem would be WebGL usage with SkiaSharp: Skia uses WebGL internally and expects those calls to happen on the "true" UI thread rather than on a web worker. |
I created You can help me by pushing it thru API review process (and have discussion about it's name on that PR). Lines 47 to 54 in 5111fdc
We will not support running managed code on UI thread, I'm confident now. At the moment the UI thread is still attached to Mono VM, but that's going to change as soon as possible.
There is toggle for allowing synchronous JSExport (with all the consequences of spin-blocking UI thread)
Net8 WASM threading is very broken, my 2c: don't do it to your users.
I would like to learn more, how to make more native scenarios possible with MT. |
How can I help with the review process? Naming looks fine, easier to understand than "OneWay". |
You can follow https://github.com/dotnet/runtime/blob/main/docs/project/api-review-process.md runtime/src/libraries/System.Runtime.InteropServices.JavaScript/src/CompatibilitySuppressions.xml Lines 15 to 20 in bc9fc5a
Which will make it public. Examples are https://github.com/dotnet/runtime/issues?q=is%3Aissue+label%3Aapi-approved+is%3Aclosed |
Will async callbacks be only supported for JSExport? We don't use those and are using If we try to change JS->.NET callbacks to return tasks, the SDK complains about What is the new intended way to pass delegates to JS side? |
That's a (known) gap. Could you please open separate issue for it and ping me there ? @kekekeks |
I'm closing this as complete for the scope of Net9 for the dotnet runtime. SummaryThe difference between Net8 and Net9 threading is that it actually works now. 😅 Status is still: experimental BlazorFor Blazor WASM, the multi-threading feature was cut for Net9. DemoI updated the Raytracer demo https://pavelsavara.github.io/dotnet-wasm-raytracer/ PerformanceWe didn't optimize for multi-threaded performance yet. Future workThere is draft of If there are bugs or missing features, please create new issue on runtime repo and ping me, we will triage and prioritize. |
@pavelsavara Is the working threading in the Net9 preview 3 from April 11? Or can we expect it in the next preview? |
The demo above is on Preview 3. |
Is any kind of debugging supposed to work with MT mode? I can't get vscode nor |
There is open issue for that #81282 and few more for broken tests |
Tracking issue for further work on JS interop and multithreading.
Constituent part of #76956
Goals
new Thread
and join it.Task.Wait
andlock()
like APIs from C# user code on all threads or non-JS/UI threadsLower priority goals
subtle
browser APINon-goals
JSWebWorker
Design discussion and experiment
Depending on selected design
PTHREAD_POOL_SIZE_STRICT=0
JSObject
proxy on correct thread later, so that it doesn't deadlock GC by dispatching to suspended threadWebAssemblyDispatcher
more async, learn from feedback [blazor][wasm] Dispatch rendering to main thread aspnetcore#48991Bugs
Memory growth, alignment
Broken tests
System.Transactions
,System.Threading
timeouts #91541WithExecutionMode
fails #91661customAttributeCount
fails #91597System.Threading.Tasks.Tests.TaskContinueWhenAnyTests.RunContinueWhenAnyTests
fails #91550System.Threading.Tasks.Tests.CancellationTokenTests.DerivedCancellationTokenSource
fails #91552CancellationTokenRegistration_DisposeAsyncWhileCallbackRunning_WaitsForCallbackToComplete
fails #91577ExceptionOnMoveNext
fails, wrong exception type #91581SimultaneousStopBreakTests
fails #91579TestParallelScheduler
fails, TaskScheduler.Default not used when no ParallelOptions are specified. #91582TaskIDFromExternalContextTest
fails, Task has a negative ID #91583Wasm.Browser.Config.Sample
,Wasm.Browser.Config.Sample
fail #91676HttpClient_CancelInDifferentThread
failing with operation cancelled #98101Wasm.Browser.EventPipe.Sample
fails #98026Process terminated
assert handling at icall.c:6162 #100249Nice to have
setTimeout
[browser][MT] Keep track of additional JS keepalive sources for worker threads #85052exit()
andabort()
Progress
Future
JSWebWorker
with external loop and JS interopJSSynchronizationContext
for UI andJSWebWorker
threadsJSSynchronizationContext
forJSObject
proxy[UnsupportedOSPlatform]
ref assemblies [browser][MT] public API of threading, like new Thread vs PNSE #88147The text was updated successfully, but these errors were encountered: