Comment Tâche.Le rendement de travail sous le capot dans Blazor WebAssembly?

0

La question

Comment Task.Yield travail sous le capot en Mono/WASM d'exécution (qui est utilisé par Blazor WebAssembly)?

Pour être clair, je crois que j'ai une bonne compréhension de la façon dont Task.Yield travaux dans .NET Framework et .NET de Base. Mono mise en œuvre n'a pas l'air très différent, en un mot, il revient à ceci:

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

Étonnamment, cela fonctionne en Blazor WebAssembly, trop (essayer en ligne):

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

Naturellement, tout cela se passe sur la même boucle d'événement de fil dans le navigateur, alors je me demande comment il fonctionne sur le niveau inférieur.

Je soupçonne, il pourrait être en utilisant quelque chose comme Emscripten de Asyncify, mais finalement, faut-il utiliser une sorte de Plate-forme Web API pour planifier une poursuite de rappel? Et si c'est le cas, exactement (comme queueMicrotask, setTimout, Promise.resove().then, etc)?


Mise à jour, je viens de découvrir que l' Thread.Sleep est mis en œuvre et il en fait des blocs de la boucle d'événement de fil

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

La meilleure réponse

5

C'est setTimeout. Il ya beaucoup d'indirection entre qui et QueueUserWorkItemmais c'est là où il touche le fond.

La plupart des WebAssembly spécifiques des machines peut être vu dans la PR 38029. Le WebAssembly mise en œuvre de RequestWorkerThread les appels à une méthode privée nommée QueueCallbackqui est mis en œuvre dans le code C, comme mono_wasm_queue_tp_cb. Ce en appelle mono_threads_schedule_background_job, qui appelle à son tour schedule_background_execqui est mis en œuvre à la Machine comme:

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

L' setTimeout rappel de la suite atteint ThreadPool.Callbackqui évoque ThreadPoolWorkQueue.Dispatch.

Le reste n'est pas spécifique à Blazor à tous, et peut être étudié en lisant le code source de la ThreadPoolWorkQueue classe. En bref, ThreadPool.QueueUserWorkItem enqueues le rappel dans une ThreadPoolQueue. Enqueueing appels EnsureThreadRequested, qui délègue à RequestWorkerThreaden tant que ci-dessus. ThreadPoolWorkQueue.Dispatch les causes d'un certain nombre de tâches asynchrones d'être retiré et exécuté; parmi eux, le callback passé à QueueUserWorkItem finalement devrait apparaître.

2021-11-28 11:17:30

Une grande réponse, tks! Mais c'est geven setTimeoutpourriez-vous expliquer un écart énorme, j'en vois lors de la synchronisation d'une boucle de await new Promise(r => setTimeout(r, 0)) avec JS interop vs une boucle de await Task.Yield? Est-il une faille dans le test? blazorrepl.telerik.com/QlFFQLPF08dkYRbm30
noseratio

queueMicrotask (par opposition à setTimeout) produit beaucoup plus conséquent: blazorrepl.telerik.com/QFbFGVFP10NWGSam57
noseratio

Je suis incapable d'ouvrir l'un des REPL liens, donc je ne peux pas dire ce que tu veux dire. Mais si vous étudiez le code source de ThreadPoolWorkQueue.Dispatchvous remarquerez qu'il y a quelques sophistiqué planification impliquées, et un setTimeout peut servir à de multiples mises en file d'attente .NET tâches asynchrones, qui je l'espère sera plus rapide que d'avoir chaque setTimeout envoi d'un rappel unique.
user3840170

Bizarre repl liens ne fonctionnent pas. Si vous souhaitez l'essayer, voici l'essentiel: gist.github.com/noseratio/73f6cd2fb328387ace2a7761f0b0dadc. C'est literrally 8000ms vs 20ms. Ensuite, il suffit de remplacer setTimeout avec queueMicrotasket c'est sur le même 20ms.
noseratio

Me semble qu'il: setTimeout rend le processus de navigateur la boucle d'événements entre les rappels, mais le .NET runtime peut envoyer plusieurs tâches asynchrones dans un seul setTimeout rappel (la file d'attente presque immédiatement après qu'ils sont mis en file d'attente), ce qui évite la surcharge de céder à la boucle d'événements. (Aussi, les navigateurs peuvent effectuer de limitation sur setTimeout les appels, qui ce dosage évite.) Cela produit un effet à peu près équivalent à queueMicrotask. Bien que les horaires que vous obtenez sont probablement pas très précis, grâce à Spectre mesures d'atténuation.
user3840170

Dans d'autres langues

Cette page est dans d'autres langues

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