How does Task.Yield work under the hood in Mono/WASM runtime (which is used by Blazor WebAssembly)?
To clarify, I believe I have a good understanding of how Task.Yield works in .NET Framework and .NET Core. Mono implementation doesn't look much different, in a nutshell, it comes down to this:
static Task Yield()
{
var tcs = new TaskCompletionSource<bool>();
System.Threading.ThreadPool.QueueUserWorkItem(_ => tcs.TrySetResult(true));
return tcs.Task;
}
Surprisingly, this works in Blazor WebAssembly, too (try it online):
<label>Tick Count: @tickCount</label><br>
@code
{
int tickCount = System.Environment.TickCount;
protected override void OnAfterRender(bool firstRender)
{
if (firstRender) CountAsync();
}
static Task Yield()
{
var tcs = new TaskCompletionSource<bool>();
System.Threading.ThreadPool.QueueUserWorkItem(_ => tcs.TrySetResult(true));
return tcs.Task;
}
async void CountAsync()
{
for (var i = 0; i < 10000; i++)
{
await Yield();
tickCount = System.Environment.TickCount;
StateHasChanged();
}
}
}
Naturally, it all happens on the same event loop thread in the browser, so I wonder how it works on the lower level.
I suspect, it might be utilizing something like Emscripten's Asyncify, but eventually, does it use some sort of Web Platform API to schedule a continuation callback? And if so, which one exactly (like queueMicrotask, setTimout, Promise.resove().then, etc)?
Updated, I've just discovered that Thread.Sleep is implemented as well and it actually blocks the event loop thread
Curious about how that works on the WebAssembly level, too. With JavaScript, I can only think of a busy loop to simulate Thread.Sleep (as Atomics.wait is only available from web worker threads).