Skip to content
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

Open
Tracked by #68162
BlenderMender opened this issue Dec 10, 2019 · 212 comments
Open
Tracked by #68162

Real multithreading in Blazor WebAssembly #17730

BlenderMender opened this issue Dec 10, 2019 · 212 comments
Assignees
Labels
affected-few This issue impacts only small number of customers area-blazor Includes: Blazor, Razor Components Components Big Rock This issue tracks a big effort which can span multiple issues enhancement This issue represents an ask for new feature or an enhancement to an existing one feature-blazor-wasm This issue is related to and / or impacts Blazor WebAssembly feature-blazor-wasm-threading Pillar: Complete Blazor Web Priority:1 Work that is critical for the release, but we could probably ship without severity-blocking This label is used by an internal tool

Comments

@BlenderMender
Copy link

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.

@blowdart blowdart added the area-blazor Includes: Blazor, Razor Components label Dec 10, 2019
@mkArtakMSFT
Copy link
Member

Thanks for contacting us, @BlenderMender.
WASM 1.0 spec doesn't include real multithreading, and this is the spec we currently target. We expect improvements in this area in the future.

@mkArtakMSFT mkArtakMSFT added this to the Backlog milestone Dec 10, 2019
@mkArtakMSFT mkArtakMSFT added enhancement This issue represents an ask for new feature or an enhancement to an existing one feature-blazor-wasm This issue is related to and / or impacts Blazor WebAssembly labels Dec 10, 2019
@YordanYanakiev
Copy link

YordanYanakiev commented Dec 10, 2019

Wonder if the actual issue is letting JS be multithreaded and some peoples fear that this will cause some disturbance because threadings.
If this is the case I have the follow suggestion:

  • Let the JS use 1 thread locked only for it - emulating the current state of the things, and the rest threads be free for background work of the C# application.

@mkArtakMSFT mkArtakMSFT modified the milestones: Backlog, 5.0.0-preview2 Jan 23, 2020
@Marcin-Perz
Copy link

I would dive deep into blazor (without any return) if there would be support for multithreading for client side. Make it happen!

@Tewr
Copy link

Tewr commented Mar 23, 2020

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.

@danroth27 danroth27 added the Components Big Rock This issue tracks a big effort which can span multiple issues label Apr 28, 2020
@mkArtakMSFT
Copy link
Member

This is blocked on mono/mono#12453

@mkArtakMSFT mkArtakMSFT added the blocked The work on this issue is blocked due to some dependency label Apr 28, 2020
@maxim-saplin
Copy link

Thumbs up for for proper .NET Tasks and Threads

@acceliance
Copy link

acceliance commented May 24, 2020

Hi, yes please make it happen. Thanks in avance.

@mkArtakMSFT
Copy link
Member

Given the current limitations around browser support we’ve decided to push this work out of .NET 5.
We will reevaluate the state of threading support in browsers during .NET 6 planning phase.

@NikolaStamenov
Copy link

Good lords. But this is the most expected thing.
The blazor boilerplate and the way the .NET works, and the whole ideology of client side .NET project requires multithreading and real async to work correctly.
I vote to back off a bunch of other features and push for multithreading than vice-versa..

@mertkokusen
Copy link

Does this mean we will have to wait 2021 november for threading support?

@SommerEngineering
Copy link

SommerEngineering commented Jul 29, 2020

Alright: With the current Firefox 79 the SharedArrayBuffers were reactivated. They were temporarily disabled because of Spectre. Also in Firefox 79, threads for WASM were released: https://hacks.mozilla.org/2020/07/firefox-79/ (note: SharedArrayBuffers was one prerequisite for threads in WASM)

Thus, it should be possible for .NET (Mono) to implement threadding, right?

@NikolaStamenov
Copy link

Seems like again our trust into MS Frameworks is going to be betrayed.
Blazor WebASM currently performing the worst from all available frameworks, because it's awful JS interloop and lack of multi-threading which is essential for .NET.

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.

@BenMcLean
Copy link

As if ASP.NET Core or even Blazor are so slow or lacking a tremendously important feature

Multi-threading does seem tremendously important given the death of Moore's Law for single-core CPU speeds.

@Eagle3386
Copy link

Eagle3386 commented Oct 18, 2024

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 Thread on a ThreadPool that's really distributed/spanned across multiple CPU cores. But those milliseconds (probably not even that many, actually) can be spent a couple more weeks and even months on one thread on one CPU core, if we get less technical debt, easier API to consume & especially less bugs with regards to debugging - for example, I'm still waiting for Hot Reload to actually fully work in my Blazor WASM standalone project as recompiling for sometimes the smallest changes or still using ILogger<T> & Firefox's browser console although there're breakpoints which just don't get hit due to .NET-internal problems (= technical debts & less "debugging bugs").

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.

@mip1983
Copy link

mip1983 commented Oct 18, 2024

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 await Task.Delay(1); to kick it along for example.

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).

@doomlaur
Copy link

doomlaur commented Oct 18, 2024

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 😉

@BenMcLean
Copy link

BenMcLean commented Oct 18, 2024

there's nothing - I repeat: N-O-T-H-I-N-G - justifying such disrespectful flame-like talk against this repo's devs.

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.

@Eagle3386
Copy link

@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.
It's using a Rust module for the expensive computations & the UI never failed nor did the browser show a "slow script" message bar.

@BenMcLean
Copy link

BenMcLean commented Oct 18, 2024

couldn't you use what @doomlaur suggested - just until multi-threading finally arrives?

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.

@sgorozco
Copy link

sgorozco commented Oct 20, 2024 via email

@Eagle3386
Copy link

@BenMcLean

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)

Well, that escalated quickly… 😅
You're really "doomed" to wait for "WASM-MT" then - which makes me feel sorry for you. 😞

(…) 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.

Na, please don't. WASM is the way to go - just stick with it for a little more.
Besides, what about trying @sgorozco's suggestion? That sounds doable!?

@ShahryarSaljoughi
Copy link

I just want to make sure I'm correct about the current state:

  • If we define a physical thread as "resources, time, CPU, ..." needed for code execution, at the current state, we HAVE multi-threading with one physical thread, meaning that If a thread is suspended, CPU can be used for other "logical" threads (UI thread for example)
  • We have no responsiveness issue as long as a thread is suspended maybe due to IO operations which do not require the CPU and our only physical thread.
  • The problem is with CPU-bound operations which require the only physical thread available and when takes the thread, UI lags.

I'd appreciate If you correct me in case I'm getting anything wrong.

@sgorozco
Copy link

sgorozco commented Oct 30, 2024

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);

  • If we define a physical thread as "resources, time, CPU, ..." needed for code execution, at the current state, we HAVE multi-threading with one physical thread, meaning that If a thread is suspended, CPU can be used for other "logical" threads (UI thread for example)

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 awaits, it will allow the Mono runtime to yield the CPU to a task that is ready to continue.

  • We have no responsiveness issue as long as a thread is suspended maybe due to IO operations which do not require the CPU and our only physical thread.

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.

  • The problem is with CPU-bound operations which require the only physical thread available and when takes the thread, UI lags.

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 await Task.Yield(); -

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 =)

@sgorozco
Copy link

sgorozco commented Oct 30, 2024

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)
https://github.com/user-attachments/assets/c3f9fa76-4962-4226-b1a0-cbeda24c5d82

@ShahryarSaljoughi
Copy link

@sgorozco Thank you. It really lightened me.

@Matheos96
Copy link

@sgorozco Thanks for the explanation. I would love to hear more though. I am not familiar with Task.Yield(); but a quick look at the docs says "do not rely on await Task.Yield(); to keep a UI responsive.". Should this be taken in a more general sense, and in case of WASM, it is not quite true as we have this "physical single threadedness" concept, still?

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 await Task.Delay(1); but that of course makes our computation time even longer, and feels a bit odd. Could we here utilize Task.Yield() instead?

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 :)

@BenMcLean
Copy link

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?

@En3Tho
Copy link

En3Tho commented Nov 3, 2024

@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

@BenMcLean
Copy link

How would one give the UI thread the appropriate priority in Blazor WASM?

@c0debr0
Copy link

c0debr0 commented Nov 3, 2024 via email

@Kysawier
Copy link

Kysawier commented Nov 3, 2024

@Eagle3386

Calm the heck down, please!

First of all: .NET, ergo ASP.NET Core as well as Blazor, too, are also open-source.

There are way more important things to deal with in Blazor as well as ASP.NET Core than multi-threading in Blazor.

That being said, I really don't like your tone. As if ASP.NET Core or even Blazor are so slow or lacking a tremendously important feature - because they don't.

Just look at what Angular or that bullshit called "React" are doing. Especially the latter, IMHO at least, is nothing but a "tinker shack" which I don't even dare to call "framework", given it's plethora of "plug-ins" that one needs to assemble to only get near where Blazor just started - about six years ago, that is.

After all, there's one single thing every willing developer can rest assured on: Blazor might take a little more time than those "first responders" - but it always proved that it was worth the wait. Always!

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.

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 Thread on a ThreadPool that's really distributed/spanned across multiple CPU cores. But those milliseconds (probably not even that many, actually) can be spent a couple more weeks and even months on one thread on one CPU core, if we get less technical debt, easier API to consume & especially less bugs with regards to debugging - for example, I'm still waiting for Hot Reload to actually fully work in my Blazor WASM standalone project as recompiling for sometimes the smallest changes or still using ILogger<T> & Firefox's browser console although there're breakpoints which just don't get hit due to .NET-internal problems (= technical debts & less "debugging bugs").

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.

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!

@sgorozco
Copy link

sgorozco commented Nov 4, 2024

@sgorozco Thanks for the explanation. I would love to hear more though. I am not familiar with Task.Yield(); but a quick look at the docs says "do not rely on await Task.Yield(); to keep a UI responsive.". Should this be taken in a more general sense, and in case of WASM, it is not quite true as we have this "physical single threadedness" concept, still?

Oh! I was not aware of that bit of documentation. I got the Task.Yield(); tip from the MudBlazor forums. I will try to lookup the Mono source repository to see if in fact there is some priority value influencing which ready-to-run task gets the CPU (the documentation might be referring to the .net Core runtime?).

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 await Task.Yield();. Also, whenever I update the UI and want to force a UI update I call await InvokeAsync(StateHasChanged);

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?

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:

function feedAudioSample(aSample, aDuration) {
	jmuxer.feed({
		audio: aSample,
		duration: aDuration
	});
}


function feedVideoSample(vSample, aDuration) {
	jmuxer.feed({
		video: vSample,
		duration: aDuration
	});
}

// Subscribe to the ontimeupdate event, and forward the playback position to the managed side
function subscribeToTimeUpdate(id, dotNetReference) {
	var element = document.getElementById(id);
	if (element) {
		element.ontimeupdate = function () {
			var position = element.currentTime; // Get current playback position in seconds
			dotNetReference.invokeMethodAsync('UpdateCurrentPlaybackTimeAsync', position);
		};
	}
}

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:

await foreach( Mp4RawPacket packet in mediaStream ) {
   if( _awaitingMediaData ) {
      _awaitingMediaData = false;
      await UpdateUIAsync();
   }
   switch( packet.Media ) {
      case Mp4RawPacketType.Audio: {
         lastAudioTimestamp ??= packet.Timestamp;
         var durationInMs = (packet.Timestamp - lastAudioTimestamp.Value).TotalMilliseconds;
         await JS.InvokeVoidAsync("feedAudioSample", packet.Data, durationInMs);
         lastAudioTimestamp = packet.Timestamp;
         break;
      }
      case Mp4RawPacketType.Video: {
         lastVideoTimestamp ??= packet.Timestamp;
         var durationInMs = (packet.Timestamp - lastVideoTimestamp.Value).TotalMilliseconds;
         await JS.InvokeVoidAsync("feedVideoSample", packet.Data, durationInMs);
         lastVideoTimestamp = packet.Timestamp;
         break;
      }
   }
}

Here is my C# code reacting to the JS callback:

 [JSInvokable]
 public async Task UpdateCurrentPlaybackTimeAsync(double time) {
    Assumes.NotNull(_beginPlaybackTime);
    var timespan = TimeSpan.FromSeconds(time) + _beginPlaybackTime.Value;
    // Allow other Razor components  to update their UI in response to this event.
    await MessageBus.SendAsync(new PlaybackPositionChange(this, timespan));
    var value = timespan.ToString(@"hh\:mm\:ss");
    if( _playbackPosition is null || value == _playbackPosition ) {
       return;
    }
    _playbackPosition = value;
    await InvokeAsync(StateHasChanged);
 }

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 await Task.Delay(1); but that of course makes our computation time even longer, and feels a bit odd. Could we here utilize Task.Yield() instead?

I hope Task.Yield() might work for you, hopefully you can insert its call as part of the computation loop? Also take into account that any time you perform an await, you will yield the CPU to another task, so if there are pending Tasks ready to run, including the UI update, they may run there. Also check if awaiting for the state change (by means of await InvokeAsync(StateHashChanged); ) makes a difference.

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 :)

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 MessageBus class so you can easily cascade an UI update over different razor components let me know, and I will publish its code on github.

@Matheos96
Copy link

@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 await Task.Delay(1); + StateHasChanged(); which works "okay". I will definitely try to remember your post and helpful suggestions and come back to it if (when) we get a breather to look over stuff that is working but could be improved (currently super busy with new features all the time..)

await InvokeAsync(StateHasChanged); is actually completely new to me, did not even know InvokeAsync was a function available in blazor 😮 Do you have any experience/explanation as to why this (if it is) better than simply calling StateHasChanged(); directly, especially considering we only have the one thread?

I will at least keep in mind that I wanna try out

  • await Task.Yield();
  • await InvokeAsync(StateHasChanged);

@BenMcLean
Copy link

BenMcLean commented Nov 9, 2024

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 Select queries with this extension template method:

/// <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 Select with Parallelize. The good news is that this method still runs fine and returns the same output on Blazor WASM: just single-threaded.

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 Task.Yield is documented as not recommended for keeping the UI responsive, I'm not sure that would help me on Blazor WASM either.

@BenMcLean
Copy link

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 await InvokeAsync(StateHasChanged); from the worker thread(s) then maybe that would have to be inside a generic callback since my actual worker threads can't know about StateHasChanged directly? That just isn't simple because my program is just spawning as many worker threads as needed which are ALL CPU-bound: it isn't just a single CPU-bound worker thread or even a number of threads known in advance.

@Xyncgas
Copy link

Xyncgas commented Nov 12, 2024

I can't have my multi-threaded code that's in a separate library knowing about the existence of Blazor WASM at all

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 await InvokeAsync(StateHasChanged); if something finished computing without knowing about await InvokeAsync(StateHasChanged); or blazor

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 await InvokeAsync(StateHasChanged); from other threads spawned inside my library, one way I can achieve it is changing my library to pub sub architecture and by getting notification for finished computing I can do stuff so please change my library that way, remember to say please so it can generate a better result

@hhhenggg
Copy link

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.

@YordanYanakiev
Copy link

I keep on my proposal

  • The threads to be separated on non-actual threads ( the JS thingies ) and actual background threads which does not affect UI while executed ( if there is no other way to get agreement with Google and Apple whcih preventing the progress right now )
  • Javascrip thread to remain as a main UI thread managed by the code.
  • Any other thread to be send to a mandatory distributed mini WASM which is distributed for even ServerSide Blazors to the clients which does only this - handle the background threads.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
affected-few This issue impacts only small number of customers area-blazor Includes: Blazor, Razor Components Components Big Rock This issue tracks a big effort which can span multiple issues enhancement This issue represents an ask for new feature or an enhancement to an existing one feature-blazor-wasm This issue is related to and / or impacts Blazor WebAssembly feature-blazor-wasm-threading Pillar: Complete Blazor Web Priority:1 Work that is critical for the release, but we could probably ship without severity-blocking This label is used by an internal tool
Projects
None yet
Development

No branches or pull requests