Unterschied zwischen Mikrotask und Makrotask innerhalb eines Ereignisschleifenkontexts


139

Ich habe gerade die Promises / A + -Spezifikation gelesen und bin auf die Begriffe Mikrotask und Makrotask gestoßen: siehe http://promisesaplus.com/#notes

Ich habe noch nie von diesen Begriffen gehört und bin jetzt gespannt, was der Unterschied sein könnte.

Ich habe bereits versucht, einige Informationen im Web zu finden, aber alles, was ich gefunden habe, ist dieser Beitrag aus dem w3.org-Archiv (der mir den Unterschied nicht erklärt): http://lists.w3.org/Archives /Public/public-nextweb/2013Jul/0018.html

Außerdem habe ich ein npm-Modul namens "macrotask" gefunden: https://www.npmjs.org/package/macrotask Auch hier ist nicht klar, was der Unterschied genau ist.

Ich weiß nur, dass es etwas mit der Ereignisschleife zu tun hat, wie in https://html.spec.whatwg.org/multipage/webappapis.html#task-queue und https: //html.spec.whatwg beschrieben .org / multipage / webappapis.html # perform-a-microtask-checkpoint

Ich weiß, dass ich angesichts dieser WHATWG-Spezifikation theoretisch in der Lage sein sollte, die Unterschiede selbst zu extrahieren. Aber ich bin sicher, dass auch andere von einer kurzen Erklärung eines Experten profitieren könnten.


Kurz gesagt: mehrere verschachtelte Ereigniswarteschlangen. Sie könnten sogar selbst eines implementieren:while (task = todo.shift()) task();
Bergi

1
Für jemanden, der ein bisschen mehr Details möchte: Geheimnisse des JavaScript Ninja, 2. Ausgabe, KAPITEL 13 Überlebende Ereignisse
Ethan

Antworten:


217

Bei einem Durchlauf der Ereignisschleife wird genau eine Aufgabe aus der Makrotask-Warteschlange verarbeitet (diese Warteschlange wird in der WHATWG-Spezifikation einfach als Aufgabenwarteschlange bezeichnet ). Nach Abschluss dieser Makrotask werden alle verfügbaren Mikrotasks verarbeitet, und zwar innerhalb desselben Durchlaufzyklus . Während diese Mikrotasks verarbeitet werden, können sie noch mehr Mikrotasks in die Warteschlange stellen, die alle einzeln ausgeführt werden, bis die Mikrotask-Warteschlange erschöpft ist.

Was sind die praktischen Konsequenzen daraus?

Wenn eine Mikrotask andere Mikrotask rekursiv in die Warteschlange stellt, kann es lange dauern, bis die nächste Makrotask verarbeitet wird. Dies bedeutet, dass Sie möglicherweise eine blockierte Benutzeroberfläche oder einen fertigen E / A-Leerlauf in Ihrer Anwendung haben.

Zumindest in Bezug auf die process.nextTick-Funktion von Node.js (die Mikrotasks in die Warteschlange stellt ) gibt es jedoch einen integrierten Schutz gegen eine solche Blockierung mithilfe von process.maxTickDepth. Dieser Wert ist auf den Standardwert 1000 eingestellt, wodurch die weitere Verarbeitung von Mikrotasks nach Erreichen dieses Grenzwerts verringert wird, wodurch die nächste Makrotask ermöglicht wird verarbeitet werden kann.

Wann sollte was verwendet werden?

Verwenden Sie Mikrotasks grundsätzlich, wenn Sie Dinge asynchron synchron ausführen müssen (dh wenn Sie sagen würden, dass Sie diese (Mikro-) Aufgabe in naher Zukunft ausführen müssen ). Ansonsten bleiben Sie bei Makrotasks .

Beispiele

Makrotasks: setTimeout , setInterval , setImmediate , requestAnimationFrame , E / A , UI-Rendering-
Mikrotasks: process.nextTick , Promises , queueMicrotask , MutationObserver


4
Obwohl es in der Ereignisschleife einen Mikrotask-Prüfpunkt gibt, stoßen die meisten Entwickler hier nicht auf Mikrotask. Mikrotasks werden verarbeitet, wenn der JS-Stapel geleert wird. Dies kann innerhalb einer Aufgabe oder sogar innerhalb der Renderschritte der Ereignisschleife häufig vorkommen.
JaffaTheCake

2
process.maxTickDepthwurde vor sehr langer Zeit entfernt: github.com/nodejs/node/blob/…
RidgeA

Sie können auch die Methode queueMicrotask () verwenden, um eine neue Mikrotask hinzuzufügen
ZoomAll

Danke @ZoomAll, wusste queueMicrotask () bis jetzt nicht. Ich habe es der Antwort hinzugefügt und Links zu all dem Zeug ...
NicBright

requestAnimationFrame (rAF) generiert nicht nur Mikrotasks. Im Allgemeinen erstellt der rAF-Aufruf eine separate Warteschlange
ZoomAll

67

Grundbegriffe in spec :

  • Eine Ereignisschleife hat eine oder mehrere Aufgabenwarteschlangen (die Aufgabenwarteschlange ist die Makrotaskwarteschlange).
  • Jede Ereignisschleife verfügt über eine Mikrotask-Warteschlange.
  • Task-Warteschlange = Makrotask-Warteschlange! = Mikrotask-Warteschlange
  • Eine Aufgabe kann in die Makrotask-Warteschlange oder die Mikrotask-Warteschlange verschoben werden
  • Wenn eine Aufgabe in eine Warteschlange (Mikro / Makro) verschoben wird, bedeutet dies, dass die Vorbereitung der Arbeit abgeschlossen ist, sodass die Aufgabe jetzt ausgeführt werden kann.

Das Prozessmodell der Ereignisschleife lautet wie folgt:

Wenn der Aufrufstapel leer ist, führen Sie die folgenden Schritte aus:

  1. Wählen Sie die älteste Aufgabe (Aufgabe A) in den Aufgabenwarteschlangen aus
  2. Wenn Aufgabe A null ist (bedeutet, dass die Aufgabenwarteschlangen leer sind), fahren Sie mit Schritt 6 fort
  3. Setzen Sie "aktuell laufende Aufgabe" auf "Aufgabe A".
  4. "Aufgabe A" ausführen (bedeutet Rückruffunktion ausführen)
  5. setze "aktuell laufende Aufgabe" auf null, entferne "Aufgabe A"
  6. Mikrotask-Warteschlange durchführen
    • (a) .Wählen Sie die älteste Aufgabe (Aufgabe x) in der Mikrotask-Warteschlange aus
    • (b) Wenn Aufgabe x null ist (bedeutet, dass die Mikrotask-Warteschlangen leer sind), springen Sie zu Schritt (g).
    • (c) .set "aktuell laufende Aufgabe" auf "Aufgabe x"
    • (d) Führen Sie "Aufgabe x" aus.
    • (e) .setze "aktuell laufende Aufgabe" auf null, entferne "Aufgabe x"
    • (f) .Wählen Sie die nächstälteste Aufgabe in der Mikrotask-Warteschlange aus und springen Sie zu Schritt (b).
    • (g) fertige Mikrotask-Warteschlange;
  7. Springe zu Schritt 1.

Ein vereinfachtes Prozessmodell lautet wie folgt:

  1. Führen Sie die älteste Aufgabe in der Makrotask-Warteschlange aus und entfernen Sie sie.
  2. Führen Sie alle verfügbaren Aufgaben in der Mikrotask-Warteschlange aus und entfernen Sie sie.
  3. nächste Runde: Führen Sie die nächste Aufgabe in der Makrotask-Warteschlange aus (Sprungschritt 2).

etwas zu merken:

  1. Wenn eine Aufgabe (in der Makrotask-Warteschlange) ausgeführt wird, werden möglicherweise neue Ereignisse registriert. So können neue Aufgaben erstellt werden. Nachfolgend sind zwei neu erstellte Aufgaben aufgeführt:
    • Der Rückruf von versprechenA.then () ist eine Aufgabe
      • versprechenA wird gelöst / abgelehnt: Die Aufgabe wird in der aktuellen Runde der Ereignisschleife in die Mikrotask-Warteschlange verschoben.
      • versprechenA steht noch aus: Die Aufgabe wird in der zukünftigen Runde der Ereignisschleife (möglicherweise in der nächsten Runde) in die Mikrotask-Warteschlange gestellt.
    • Der Rückruf von setTimeout (callback, n) ist eine Aufgabe und wird in die Makrotask-Warteschlange verschoben, auch wenn n 0 ist.
  2. Die Aufgabe in der Mikrotask-Warteschlange wird in der aktuellen Runde ausgeführt, während die Aufgabe in der Makrotask-Warteschlange auf die nächste Runde der Ereignisschleife warten muss.
  3. Wir alle wissen, dass der Rückruf von "click", "scroll", "ajax", "setTimeout" ... Aufgaben sind. Wir sollten uns jedoch auch daran erinnern, dass js-Codes als Ganzes im Skript-Tag auch eine Aufgabe (eine Makrotask) sind.

2
Das ist eine großartige Erklärung! Danke für das Teilen!. Eine weitere Sache, die zu erwähnen ist , ist in NodeJs , setImmediate()ist Makro / Aufgabe und process.nextTick()ist ein Mikro / Job.
LeOn - Han Li

6
Was ist mit Browseraufgaben paint? In welche Kategorie würden sie passen?
Legenden

Ich denke, sie würden in Mikroaufgaben passen (wie requestAnimationFrame)
Divyanshu Maithani

Hier ist die Reihenfolge, in der die v8-Ereignisschleife ausgeführt wird -> Call Stack || Mikroaufgaben || Task Queue || rAF || Baum rendern || Layout || Malen || <OS Native-Aufrufe zum Zeichnen der Pixel auf einem Bildschirm> <----- 1) DOM (neue Änderungen), CSSOM (neue Änderungen), Renderbaum, Layout und Paint erfolgen nach dem Rückruf von requestAnimationFrame gemäß den Timern der Ereignisschleife. Aus diesem Grund ist es wichtig, dass Sie Ihre DOM-Vorgänge so oft wie möglich vor rAF beenden. Der Rest kann in rAF erfolgen. PS: Wenn Sie rAF aufrufen, wird eine Makro-Task-Ausführung ausgelöst.
Anvesh Checka

Ich weiß nicht, ob ich mich irre, aber ich stimme dieser Antwort nicht zu. Mikrotasks werden vor der Makrotask ausgeführt. codepen.io/walox/pen/yLYjNRq ?
Walox

9

Ich denke, wir können die Ereignisschleife nicht getrennt vom Stapel diskutieren, also:

JS hat drei "Stapel":

  • Standardstapel für alle synchronen Aufrufe (eine Funktion ruft eine andere auf usw.)
  • Mikrotask-Warteschlange (oder Job-Warteschlange oder Mikrotask-Stapel) für alle asynchronen Vorgänge mit höherer Priorität (process.nextTick, Promises, Object.observe, MutationObserver)
  • Macrotask-Warteschlange (oder Ereigniswarteschlange, Task-Warteschlange, Macrotask-Warteschlange) für alle asynchronen Vorgänge mit niedrigerer Priorität (setTimeout, setInterval, setImmediate, requestAnimationFrame, E / A, UI-Rendering)
|=======|
| macro |
| [...] |
|       |
|=======|
| micro |
| [...] |
|       |
|=======|
| stack |
| [...] |
|       |
|=======|

Und die Ereignisschleife funktioniert folgendermaßen:

  • Führen Sie alles von unten nach oben vom Stapel aus aus. Überprüfen Sie NUR, wenn der Stapel leer ist, was in den obigen Warteschlangen vor sich geht
  • Überprüfen Sie den Mikrostapel und führen Sie dort (falls erforderlich) alles mit Hilfe des Stapels aus, eine Mikroaufgabe nach der anderen, bis die Mikrotask-Warteschlange leer ist oder keine Ausführung erforderlich ist, und überprüfen Sie NUR dann den Makrostapel
  • Überprüfen Sie den Makro-Stack und führen Sie dort (falls erforderlich) alles mit Hilfe des Stacks aus

Der Mico-Stack wird nicht berührt, wenn der Stack nicht leer ist. Der Makrostapel wird nicht berührt, wenn der Mikrostapel nicht leer ist ODER keine Ausführung erfordert.

Zusammenfassend lässt sich sagen: Die Mikrotask-Warteschlange entspricht fast der Makrotask-Warteschlange, aber diese Aufgaben (process.nextTick, Promises, Object.observe, MutationObserver) haben eine höhere Priorität als Makrotask.

Mikro ist wie Makro, aber mit höherer Priorität.

Hier haben Sie "ultimativen" Code, um alles zu verstehen.

console.log('stack [1]');
setTimeout(() => console.log("macro [2]"), 0);
setTimeout(() => console.log("macro [3]"), 1);

const p = Promise.resolve();
for(let i = 0; i < 3; i++) p.then(() => {
    setTimeout(() => {
        console.log('stack [4]')
        setTimeout(() => console.log("macro [5]"), 0);
        p.then(() => console.log('micro [6]'));
    }, 0);
    console.log("stack [7]");
});

console.log("macro [8]");

/* Result:
stack [1]
macro [8]

stack [7], stack [7], stack [7]

macro [2]
macro [3]

stack [4]
micro [6]
stack [4]
micro [6]
stack [4]
micro [6]

macro [5], macro [5], macro [5]
--------------------
but in node in versions < 11 (older versions) you will get something different


stack [1]
macro [8]

stack [7], stack [7], stack [7]

macro [2]
macro [3]

stack [4], stack [4], stack [4]
micro [6], micro [6], micro [6]

macro [5], macro [5], macro [5]

more info: https://blog.insiderattack.net/new-changes-to-timers-and-microtasks-from-node-v11-0-0-and-above-68d112743eb3
*/

1
Das Aufrufen einer Warteschlange als Stapel ist völlig verwirrend.
Bergi
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.