-
Notifications
You must be signed in to change notification settings - Fork 10k
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
Real multithreading in Blazor WebAssembly #17730
Comments
Thanks for contacting us, @BlenderMender. |
Wonder if the actual issue is letting JS be multithreaded and some peoples fear that this will cause some disturbance because threadings.
|
I would dive deep into blazor (without any return) if there would be support for multithreading for client side. Make it happen! |
I've created a small library called BlazorWorker which creates a new dotnet process using web workers. It's very similar to multithreading, main difference being no shared memory, only message passing @BlenderMender @Marcin-Perz I would appreciate your feedback on the API, to see if it's a path worth pursuing in the meantime. |
This is blocked on mono/mono#12453 |
Thumbs up for for proper .NET Tasks and Threads |
Hi, yes please make it happen. Thanks in avance. |
Given the current limitations around browser support we’ve decided to push this work out of .NET 5. |
Good lords. But this is the most expected thing. |
Does this mean we will have to wait 2021 november for threading support? |
Alright: With the current Firefox 79 the Thus, it should be possible for .NET (Mono) to implement threadding, right? |
Seems like again our trust into MS Frameworks is going to be betrayed. Please MS team - do something. Do not kill it before it's born, and do it now, December is going to be awfully late, since the disappointment will start taking space. |
Multi-threading does seem tremendously important given the death of Moore's Law for single-core CPU speeds. |
You're kidding, right? Because if not, there's nothing but laughter on anyone's end who has an actual real-world usage going on. There's even Steve's BlazeOrbital demo - running on .NET 6, that is - showcasing how "much" even such a demanding single-thread WASM app (BTW: actually using a Rust-based module via inter-op as even more load on that one thread!) really needs - barely anything lasts a second, let alone more! So, again: put the fancy pants aside & there's nothing - I repeat: N-O-T-H-I-N-G - justifying such disrespectful flame-like talk against this repo's devs. Period! It might be technically nice to actually run that Now that's something that just sucks & needs attention right away - and .NET 9 will solve those first, for which I'm more than grateful. Once again. |
I'm looking forward to threading from the point of view of consistency between the timing/behavior you get of components between server and client rendering (like if you want to use 'InteractiveAuto'). I've run into things where the behavior is ok with an 'InteractiveServer' render, but on the web assembly side something seems to get stuck or not update and you need a little hacky More performance would be nice to, though I think most of my performance woes come from a lot of JS-interop from a third party Blazor component library, and I guess multi-threading on it's own won't help there (or maybe it will, what do I know). But yea, no point arguing about it, they're working on it an I'm sure it's never as simple as you imagine. I appreciate any quality of life improvements that might materialize in .NET 9 and beyond, they've said they're working on the IDE experience, compiler and hot reload (though debugging perf is currently trashed in RC2/VS2022 preview, hopefully it's just pre-release work in progress stuff). |
First of all, please keep a respectful tone. No one benefits from offending the devs or anyone else, and it won't speed up the development of this feature either. I would like to explain some of the use cases I had in the past. My issue was that I wanted to use Blazor and, in some cases, I wanted to perform certain expensive computations that might take up to multiple seconds - or a minute - in the browser. Of course, doing this without a separate thread would result in the UI thread being blocked, so the user would see a message from the browser that a script runs for a long time, asking whether it should be stopped. This looks very unprofessional to the user, while the user thinks that something went wrong - instead of showing a progress bar that gets constantly updated, for example. While such expensive computations could also be done remotely (on a server), in certain cases, the response time would be too long, decreasing usability. This is because some computations might be very short (and, in such cases, the website has to be super responsive - that is, the computation has to run in the browser, since a call to a server would have a huge overhead), while other computations might take a lot longer (in which case the UI thread of the website MUST NOT be blocked). However, for my use case I decided that either JavaScript + Web Workers, or WebAssembly together with some other WebAssembly-compatible language (e.g. Rust) fits this use case better. Note: I did not try to use .NET again for such use cases since a few years (.NET 5 and 6). My point is: while most projects might not need multithreading, there are certain use cases that do benefit from it 😉 |
I didn't mean to comment on the unprofessional tone issue. I was just stating that multi-threading is important. I'm trying to do image processing in a Blazor WASM application (so the image processing is done locally, in the browser, with no server backend, just a static PWA doing all of the actual work in the browser) and the same operation which runs in 1.4 seconds on desktop can take 40+ seconds on WASM because it's single threaded. Plus, there's no obvious way (so far as I have been able to discern) to provide a responsive UI for while the user has to wait on their image to finish processing so the browser appears to be frozen or crashed that whole time. I see a lot of hacky workarounds people are making online but the ideal solution to keep the code clean would be to just support multi-threading using web workers on Blazor WASM. |
@BenMcLean Hmm, couldn't you use what @doomlaur suggested - just until multi-threading finally arrives? @doomlaur That's exactly why I pointed Steve Sanderson's BlazeOrbital (see link above) repo out. |
You mean stop using C# and instead re-write my entire app in Rust or Javascript? ("just the multi-threaded portions" means essentially "the entire app" in this case) Yes, technically I could do that if I wanted to, but since the Blazor WASM app is just slapping a very small addition of a GUI front-end onto a much larger very idiomatic C# codebase with LINQ and memory streams and serialization and reflection and other stuff that could be non-trivial to port to another language, I'd sooner just abandon the web front-end altogether than to invest in such a massive re-write. I'd just make a desktop app and tell users that the desktop app is the only app. |
Interspersed calls to Task.Yield() in your computation loop helps the UI
stay responsive, not optimal, but it may help. It reminds me of old days
where we had to call DoEvents() to allow the Cancel button to react.
…On Fri, Oct 18, 2024, 12:59 PM Laurențiu Leahu-Vlăducu < ***@***.***> wrote:
First of all, please keep a respectful tone. No one benefits from
offending the devs, and it won't speed up the development of this feature
either.
I would like to explain some of the use cases I had in the past. My issue
was that I wanted to use Blazor and, in some cases, I wanted to perform
certain expensive computations that might take up to multiple seconds - or
a minute - in the browser. Of course, doing this without a separate thread
would result in the UI thread being blocked, so the user would see a
message from the browser that a script runs for a long time, asking whether
it should be stopped. This looks very unprofessional to the user, while the
user thinks that something went wrong - instead of showing a progress bar
that gets constantly updated, for example. While such expensive
computations could also be done remotely (on a server), in certain cases,
the response time would be too long, decreasing usability. This is because
some computations might be very short (and, in such cases, the website has
to be super responsive - that is, the computation has to run in the
browser, since a call to a server would have a huge overhead), while other
computations might take a lot longer (in which case the UI thread of the
website MUST NOT be blocked).
However, for my use case I decided that either JavaScript + Web Workers,
or WebAssembly together with some other WebAssembly-compatible language
(e.g. Rust) fits this use case better. Note: I did not try to use .NET
again for such use cases since a few years (.NET 5 and 6).
My point is: while most projects might not need multithreading, there are
certain use cases that do benefit from it 😉
—
Reply to this email directly, view it on GitHub
<#17730 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ABRR7UQPDLSJP473K4JEY5TZ4FEBVAVCNFSM4JY3KBN2U5DIOJSWCZC7NNSXTN2JONZXKZKDN5WW2ZLOOQ5TENBSGI4TONBSGE4Q>
.
You are receiving this because you are subscribed to this thread.Message
ID: ***@***.***>
|
Well, that escalated quickly… 😅
Na, please don't. WASM is the way to go - just stick with it for a little more. |
I just want to make sure I'm correct about the current state:
I'd appreciate If you correct me in case I'm getting anything wrong. |
Disclaimer: I'm not sure if what I am about to say applies to AOT builds, (unfortunately I have not been able to enable it in my projects);
When running under the Mono runtime under WASM, indeed, we only have a single thread. Every bit of work, including garbage collection, is performed by this single thread. Suspending it in the real sense will deadlock your application (unless some timeout occurs, or the awaited event is external to our logic). However, if we strictly follow the async-await pattern (non-blocking I/O), where we are operating in terms of promises (Tasks in .Net lingo), then your assertion is right - the CPU will be able to be shared between the tasks - in this case, whenever one of your tasks
We have no responsiveness issue as long as every concurrent task performing I/O awaits for it; any blocking I/O operation will freeze the entire application, including the UI.
Yes, if you peg your only thread with an intensive CPU operation, the UI will lag, however (as I mentioned in an earlier post ), you can allow other ready-to-run tasks to continue (including the UI update logic) if inside your CPU-bound operation you yield the CPU by performing calls to Hope this make things a little bit clearer, and hopefully someone with knowledge regarding the AOT runtime environment can also bring some light to the table =) |
Here is some proof that this way of "cooperative multitasking with a single thread" works - as long as you always choose to perform async I/O (and allow other tasks to run in CPU-heavy scenarios. Here you can see that I am performing audio playback - UI manipulation AND some heavy-duty CPU bound stuff (automatic audio recognition) - all with a single thread available (and no AOT compilation) |
@sgorozco Thank you. It really lightened me. |
@sgorozco Thanks for the explanation. I would love to hear more though. I am not familiar with Also, may be a dumb question but counts as "CPU bound work" in practise? For example, in our application we do quite a lof o calculations, and when we get partial results of these we perform JS interop as we are building a model in Javascript (basically wrapping a JS library). To my understanding, the JS interop has to happen from the UI thread, though, that should be the only thread right (this may be irrelevant to my question)? Either way, if I would wrap such thinks with Task.Run and then await them, would that keep my UI responsive during the calculations? Of course, we also want to update a progressbar throughout these calculations, which is another challenge, as the updating of the progressbar of course needs to be its own logic, and happens synchronously in relation to the calculations. The issue we often have is that the code that updates the UI + StateHasChanged() executes so quickly so the CPU bound calculations have time to take over again before the UI has time to update. At times, we have kind of "hackily" solved this with some Sorry if I seem like I am all over the place, because I am. Concurrency on a single thread is quite hard to grasp imo. Multithreaded concurrency is way easier to imagine :) |
Yeah, the docs say not to rely on Task.Yield to keep a UI responsive so I haven't. But if the docs are wrong then maybe I should? |
@Matheos96 Async is basically a "callback orchestrator". Just imagine a collection of actions/callbacks. Once action is complete another one (a callback) should start. You give it a nice syntax and there you go - async. On a single thread you have 1 producer and 1 consumer. On multuple threads you can have different configurations but generally it doesn't really change the fact that conceptually it's a collection of action/callbacks. How do you execute those is rather an implementation detail. Generally cooperative execution is about chunking your work into multiple pieces and yielding (e.g. just putting a action/callback into the queue) to let other actions/callbacks get executed too. Simply wrapping your js call into a task won't help because if there is a lot going on on js side (e.g. lots of compute for example) then your thread will just end up doing that work until it completes. There is nothing cooperative about that. Remark about yielding was there because some of schedulers prioritize actual "work" over events like user input or rendering (it goes last usually). Thats why yielding mihht not work - you callback might just execute right away due to higher priority. These are impl details though. Might work, might not, might work occasionally. Doing Task.delay has better change of letting other actions complete bevause there is an actualy delay between scheduling and executing and it cant start right away. Basically what you want is to yield with a lower priority but it is an impl detail of scheduler. Maybe a custom sync context or task scheduler could help. Also you might play with timers or rate limiters instead of doing delays to make things more streamlined if you don't actually need to compute that much |
How would one give the UI thread the appropriate priority in Blazor WASM? |
This actually makes sense and what operating systems do. But what if you
have a task that can't be broken into chunks?
"
Generally cooperative execution is about chunking your work into multiple
pieces and yielding (e.g. just putting a action/callback into the queue) to
let other actions/callbacks get executed too."
…On Sun., Nov. 3, 2024, 2:19 a.m. Igor Bagdamyan, ***@***.***> wrote:
@Matheos96 <https://github.com/Matheos96> Async is basically a "callback
orchestrator".
Just imagine a collection of actions/callbacks. Once action is complete
another one (a callback) should start. You give it a nice syntax and there
you go - async.
On a single thread you have 1 producer and 1 consumer. On multuple threads
you can have different configurations but generally it doesn't really
change the fact that conceptually it's a collection of action/callbacks.
How do you execute those is rather an implementation detail.
Generally cooperative execution is about chunking your work into multiple
pieces and yielding (e.g. just putting a action/callback into the queue) to
let other actions/callbacks get executed too.
Simply wrapping your js call into a task won't help because if there is a
lot going on on js side (e.g. lots of compute for example) then your thread
will just end up doing that work until it completes. There is nothing
cooperative about that.
Remark about yielding was there because some of schedulers prioritize
actual "work" over events like user input or rendering (it goes last
usually). Thats why yielding mihht not work - you callback might just
execute right away due to higher priority. These are impl details though.
Might work, might not, might work occasionally.
Doing Task.delay has better change of letting other actions complete
bevause there is an actualy delay between scheduling and executing and it
cant start right away.
—
Reply to this email directly, view it on GitHub
<#17730 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AUWLCYGK4R7W6B5A47A52GDZ6XFBZAVCNFSM4JY3KBN2U5DIOJSWCZC7NNSXTN2JONZXKZKDN5WW2ZLOOQ5TENBVGMZTENJRGI2Q>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
Wooo, what a comment. I like your Chihuahua attitude. First of all: He didn't mention anything about Blazor or .NET being closed source; he just pointed out that there is this open source library that can do this. I think that from an adoption point, multithreading is a very important topic that is endlessly postponed and has very valid use cases that need it to work with Blazor WASM. For example, OpenTelemetry requires it to work. There are reasons why a lot of known people in the .NET community suggest waiting before adopting Blazor, and it's one of the things that are bothering people a lot. I like how you call others to calm down because they "attack" your favorite tech, even if they haven't done anything, and later go and rant about how their point is superstitious and other technologies are "bullshit" and "tinker shack" because there can be only one that you like, even if it's not even fully baked.
Yes, multithreading does have real-world usage, even if YOU don't use it in your codebase, so no, he wasn't kidding. Also, I don't like how you are spiteful to people and mock them. You know that there are technologies that just can't run single-threaded? The fact that you can do a lot on a single thread after optimization is a whole different thing. I don't think that anyone - I repeat: A-N-Y-O-N-E said or justified disrespectful flame-like talk against this repo's devs. The only flame-spitting and ostracizing person in this conversation is you. Period! |
Oh! I was not aware of that bit of documentation. I got the The pattern that has worked for me is the following: I start the CPU-heavy operation in its own Task via Task.Run(). It usually is a loop, and inside the loop, after N iterations (the number depending on the average elapsed time per iteration) I check for a possibly asserted CancellationToken and also invoke
As far as I understand, you can directly await for the JS interop to occur. Although most of my UI updates are handled by the MudBlazor library, for audio and video playback I am relying on the very neat jmuxer library, so I guess this is somewhat similar to your described use-case. I have these three JS methods for doing Interop with the jumuxer library:
The first two methods allow me to push managed data to the JS side, while the third one allows me to notify the managed side when there has been a playback time update event. Here is my C# code pushing managed data to the JS side:
Here is my C# code reacting to the JS callback:
I hope
No worries, I really would like to help you with any issue. I am no expert and surely, I have as many doubts as you have!, however I am happy with how "reactive" my applications are, even with the limitations of a single-thread, and maybe my experience may be of help. If you would like to integrate our |
@sgorozco Thanks a ton for the detailed response! Due to time constraints we don't actively pursue improving this at this very moment, I was mainly asking in order to get new ideas for future improvements in my head :) Today we use
I will at least keep in mind that I wanna try out
|
I feel more confused than ever about how to do the workaround to keep the UI responsive in Blazor WASM. When I started writing the library I'm working on, I didn't understand how to do asynchronous programming at all, so once I started trying to optimize it, I actually just started replacing some /// <summary>
/// Parallelizes the execution of a Select query while preserving the order of the source sequence.
/// </summary>
public static List<TResult> Parallelize<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector) => source
.Select((element, index) => (element, index))
.AsParallel()
.Select(sourceTuple => (result: selector(sourceTuple.element), sourceTuple.index))
.OrderBy(resultTuple => resultTuple.index)
.AsEnumerable()
.Select(resultTuple => resultTuple.result)
.ToList(); It works great when you know that none of the items in the sequence depend on each other or on anything that could be changed while it's running. Now my program has a lot of those and after adding it, many operations take a small fraction of the time they used to take when on a properly multi-threaded runtime but of course this doesn't help me on Blazor WASM at all. It's kind of a Band-Aid for code that was written when I knew LINQ but not asynchronous programming. I just replace I am thinking to perhaps eventually add asynchronous methods with progress reporting and cancellation tokens for the work my library does since I've learned how to do those things more recently, but since |
I forgot to mention in my last post that I can't have my multi-threaded code that's in a separate library knowing about the existence of Blazor WASM at all. If I need to run |
I understand it might be a common situation and I say I would restructure my application so it has a pub sub architecture so you can run Simply put the library to Github Copilot and ask it the change it to a pub sub architecture should be worth trying, takes a couple $ and a couple seconds, nothing hurts You can ask the AI something like I want to update the UI by calling |
It's been 5 years and we've been discussing it, but this feature has been indefinitely postponed. For using blazor, many developers are concerned about this feature and it is difficult to say that it is not very effective. I hope the team can increase its priority so that it can be launched as a milestone feature in Net10. |
I keep on my proposal
|
Is your feature request related to a problem? Please describe.
I have load of CPU intensive requests connected to vial for the application data from sensors, and I would like to have a real multithreading for multiple sources which streams large amount of data nonstop.
Describe the solution you'd like
Multithreading which is available already in WEBASM to be exposed to Blazor Client side.
The text was updated successfully, but these errors were encountered: