Task.Yield 在 Blazor WebAssembly 中如何工作?

nos*_*tio 8 .net c# async-await webassembly blazor

Mono/WASM 运行时(由 Blazor WebAssembly 使用)的Task.Yield底层如何工作?

澄清一下,我相信我对.NET Framework 和 .NET Core 的工作原理有很好的了解。Task.YieldMono 实现看起来并没有太大不同,简而言之,它可以归结为:

static Task Yield() 
{
    var tcs = new TaskCompletionSource<bool>();
    System.Threading.ThreadPool.QueueUserWorkItem(_ => tcs.TrySetResult(true));
    return tcs.Task;
}
Run Code Online (Sandbox Code Playgroud)

令人惊讶的是,这也适用于 Blazor WebAssembly(在线尝试):

<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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

当然,这一切都发生在浏览器中的同一个事件循环线程上,所以我想知道它在较低级别上是如何工作的。

我怀疑,它可能正在利用Emscripten 的 Asyncify之类的东西,但最终,它是否使用某种 Web 平台 API 来安排连续回调?如果是这样,具体是哪一个(如queueMicrotasksetTimoutPromise.resove().then等)?


更新了,我刚刚发现它Thread.Sleep也被实现了,它实际上阻塞了事件循环线程

我也想知道它在 WebAssembly 级别上是如何工作的。对于 JavaScript,我只能想到一个繁忙的循环来模拟Thread.Sleep(只能Atomics.wait从 Web 工作线程中获得)。

use*_*170 9

它\xe2\x80\x99s setTimeout。和 之间有相当大的间接性QueueUserWorkItem,但这是它的最低点。

\n

大多数特定于 WebAssembly 的机制可以在PR 38029中看到。WebAssembly 实现RequestWorkerThread调用名为 的私有方法QueueCallback,该方法在 C 代码中实现为mono_wasm_queue_tp_cb。这会调用mono_threads_schedule_background_job,后者又调用schedule_background_exec,它在 TypeScript 中实现为:

\n
export function schedule_background_exec(): void {\n    ++pump_count;\n    if (typeof globalThis.setTimeout === "function") {\n        globalThis.setTimeout(pump_message, 0);\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

回调setTimeout最终到达ThreadPool.Callback,从而调用ThreadPoolWorkQueue.Dispatch.

\n

其余部分根本不是 Blazor 特有的,可以通过阅读该类的源代码来研究ThreadPoolWorkQueue。简而言之,ThreadPool.QueueUserWorkItem将回调排队到ThreadPoolQueue. 排队调用EnsureThreadRequested,委托给RequestWorkerThread,实现如上。 ThreadPoolWorkQueue.Dispatch导致一些异步任务出队并执行;其中,传递给的回调QueueUserWorkItem最终应该出现。

\n