Jak se Úkol.Výnos práce pod kapotou v Blazor WebAssembly?

0

Otázka

Jak se Task.Yield práce pod kapotou v Mono/WASM runtime (který je používán Blazor WebAssembly)?

Pro upřesnění, myslím, že mít dobré znalosti o tom , jak Task.Yield funguje .NET Framework a .NET Jádro. Mono provedení nevypadá příliš neliší, v kostce, to přijde na to:

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

Překvapivě to funguje v Blazor WebAssembly, také (zkuste si to on-line):

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

Přirozeně, to vše se děje na stejné události smyčky niti v prohlížeči, tak by mě zajímalo, jak to funguje na nižší úrovni.

Mám podezření, to by mohlo být využití něco jako Emscripten je Asyncify, ale nakonec se to použít nějaký Web Platform API naplánovat pokračování zpětného volání? A pokud ano, který z nich přesně (jako queueMicrotask, setTimout, Promise.resove().thenatd.)?


Aktualizováno, právě jsem zjistil, že Thread.Sleep je implementován jako no a to vlastně bloky případě smyčky nitě

.net async-await blazor c#
2021-11-24 06:13:47
1

Nejlepší odpověď

5

To je setTimeout. Tam je značná dereference mezi tím a QueueUserWorkItem, ale to je místo, kde je dna.

Většina WebAssembly-zvláštních strojů může být viděn v PR 38029. Na WebAssembly provádění RequestWorkerThread volá privátní metodu s názvem QueueCallback, který je implementován v C kódu jako mono_wasm_queue_tp_cb. To vyvolá v mono_threads_schedule_background_job, což volá schedule_background_exec, který je realizován na Stroji, jako jsou:

export function schedule_background_exec(): void {
    ++pump_count;
    if (typeof globalThis.setTimeout === "function") {
        globalThis.setTimeout(pump_message, 0);
    }
}

Na setTimeout callback nakonec dosáhne ThreadPool.Callback, které vyvolá ThreadPoolWorkQueue.Dispatch.

Zbytek není specifické pro Blazor vůbec, a mohou být studovány čtení zdrojového kódu ThreadPoolWorkQueue třídy. Stručně řečeno, ThreadPool.QueueUserWorkItem enqueues zpětné volání v ThreadPoolQueue. Enqueueing hovory EnsureThreadRequested, které delegáti RequestWorkerThreadrealizován jako výše. ThreadPoolWorkQueue.Dispatch způsobuje nějaké číslo asynchronní úkoly, které mají být dequeued a popraven; mezi nimi, zpětné volání předán QueueUserWorkItem by se nakonec objeví.

2021-11-28 11:17:30

Skvělá odpověď, tks! Ale dát to setTimeoutmohl byste vysvětlit velký rozdíl vidím při časování smyčky await new Promise(r => setTimeout(r, 0)) s JS interop vs smyčky await Task.Yield? Je tam chyba v testu? blazorrepl.telerik.com/QlFFQLPF08dkYRbm30
noseratio

queueMicrotask (na rozdíl od setTimeout) produkuje mnohem blíže výsledek: blazorrepl.telerik.com/QFbFGVFP10NWGSam57
noseratio

Nejsem schopen otevřít některý z REPL odkazy, takže nemůžu říct, co máte na mysli. Ale pokud budete studovat zdrojový kód ThreadPoolWorkQueue.Dispatch, zjistíte, že tam je nějaké sofistikované plánování zapojit, jak dobře, a jeden setTimeout může sloužit více ve frontě .NET asynchronní úkoly, které bych očekávat, že bude rychlejší, než mít každý setTimeout odeslání jednoho zpětného volání.
user3840170

Zvláštní repl odkazy nefungují. Pokud byste ještě chtěli vyzkoušet, tady je podstata: gist.github.com/noseratio/73f6cd2fb328387ace2a7761f0b0dadc. Je to literrally 8000ms vs 20ms. Pak stačí vyměnit setTimeout s queueMicrotaska je to asi stejné, 20ms.
noseratio

Vypadá to, že: setTimeout dělá prohlížeč proces případě smyčky mezi zpětná volání, ale .NET runtime může odeslání více asynchronních úloh v jednom setTimeout zpětné volání (dequeuing je téměř okamžitě poté, co jsou ve frontě), a tak se vyhnout režii dávat k event loop. (Také, prohlížeče, může provést omezení na setTimeout hovory, které tento dávkovací vyhýbá.) To vytváří efekt, hrubě ekvivalentní k queueMicrotask. I když časování dostanete se asi není moc přesné, díky Spectre skutečnosti snižující závažnost rizika.
user3840170

V jiných jazycích

Tato stránka je v jiných jazycích

Русский
..................................................................................................................
Italiano
..................................................................................................................
Polski
..................................................................................................................
Română
..................................................................................................................
한국어
..................................................................................................................
हिन्दी
..................................................................................................................
Français
..................................................................................................................
Türk
..................................................................................................................
Português
..................................................................................................................
ไทย
..................................................................................................................
中文
..................................................................................................................
Español
..................................................................................................................
Slovenský
..................................................................................................................