Różnica między mikrozadaniem a makrozadaniem w kontekście pętli zdarzeń

140

Właśnie skończyłem czytać specyfikację Promises / A + i natknąłem się na terminy microtask i macrotask: patrz http://promisesaplus.com/#notes

Nigdy wcześniej nie słyszałem o tych terminach, a teraz jestem ciekawy, jaka może być różnica?

Próbowałem już znaleźć jakieś informacje w sieci, ale znalazłem tylko ten post z archiwów w3.org (który nie wyjaśnia mi różnicy): http://lists.w3.org/Archives /Public/public-nextweb/2013Jul/0018.html

Dodatkowo znalazłem moduł npm o nazwie „macrotask”: https://www.npmjs.org/package/macrotask Ponownie nie jest wyjaśnione, na czym dokładnie polega różnica.

Wiem tylko, że ma to coś wspólnego z pętlą zdarzeń, jak opisano w https://html.spec.whatwg.org/multipage/webappapis.html#task-queue i https: //html.spec.whatwg .org / multipage / webappapis.html # perform-a-microtask-checkpoint

Wiem, że teoretycznie powinienem być w stanie samodzielnie wyodrębnić różnice, biorąc pod uwagę tę specyfikację WHATWG. Ale jestem pewien, że inni również mogliby skorzystać z krótkiego wyjaśnienia udzielonego przez eksperta.

NicBright
źródło
W skrócie: wiele zagnieżdżonych kolejek zdarzeń. Możesz nawet wdrożyć jeden samodzielnie:while (task = todo.shift()) task();
Bergi
1
Dla kogoś, kto chce trochę więcej szczegółów: Secrets of the JavaScript Ninja, 2nd Edition, CHAPTER 13 Surviving events
Ethan

Odpowiedzi:

220

Jedno obejście pętli zdarzeń spowoduje wykonanie dokładnie jednego zadania z kolejki makrozadań (kolejka ta jest nazywana po prostu kolejką zadań w specyfikacji WHATWG ). Po zakończeniu tego makrozadania wszystkie dostępne mikrozadania zostaną przetworzone, a mianowicie w ramach tego samego cyklu odejścia na drugi krąg . Podczas przetwarzania tych mikrozadań mogą one ustawiać w kolejce jeszcze więcej mikrozadań, z których wszystkie będą uruchamiane jedno po drugim, aż do wyczerpania kolejki mikrozadań.

Jakie są tego praktyczne konsekwencje?

Jeśli mikrozadanie rekurencyjnie kolejkuje inne mikrozadania, przetworzenie następnego makrozadania może zająć dużo czasu. Oznacza to, że możesz skończyć z zablokowanym interfejsem użytkownika lub niektórymi zakończonymi I / O bezczynnymi w aplikacji.

Jednak przynajmniej jeśli chodzi o funkcję process.nextTick Node.js (która kolejkuje mikrozadania ), istnieje wbudowana ochrona przed takim blokowaniem za pomocą process.maxTickDepth. Ta wartość jest ustawiona na wartość domyślną 1000, co ogranicza dalsze przetwarzanie mikrozadań po osiągnięciu tego limitu, co pozwala na przetworzenie następnego makrozadania )

Kiedy więc czego użyć?

Zasadniczo używaj mikrozadań gdy musisz wykonać coś asynchronicznie w sposób synchroniczny (tj. Gdy powiedziałbyś, że wykonaj to (mikro) zadanie w najbliższej przyszłości ). W przeciwnym razie trzymaj się makrozadań .

Przykłady

macrotasks: setTimeout , setInterval , setImmediate , requestAnimationFrame , I / O , renderowanie UI
mikrozadania: process.nextTick , Promises , queueMicrotask , MutationObserver

NicBright
źródło
4
Chociaż w pętli zdarzeń znajduje się punkt kontrolny mikrozadań, większość programistów nie napotyka mikrozadań. Mikrozadania są przetwarzane po opróżnieniu stosu JS. Może się to zdarzyć wiele razy w ramach zadania lub nawet w ramach kroków renderowania pętli zdarzeń.
JaffaTheCake
2
process.maxTickDepthzostał usunięty bardzo dawno temu: github.com/nodejs/node/blob/…
RidgeA
możesz również użyć metody queueMicrotask () , aby dodać nowe mikrozadanie
ZoomAll
Dzięki @ZoomAll, do tej pory nie znałem queueMicrotask (). Dodałem to do odpowiedzi plus linki do wszystkich rzeczy ...
NicBright
requestAnimationFrame (rAF) nie tylko generuje mikrozadania. Ogólnie rzecz biorąc, wywołanie rAF tworzy osobną kolejkę
ZoomAll
67

Podstawowe pojęcia w specyfikacji :

  • Pętla zdarzeń ma co najmniej jedną kolejkę zadań. (Kolejka zadań to kolejka makrozadań)
  • Każda pętla zdarzeń ma kolejkę mikrozadań.
  • kolejka zadań = kolejka makrozadań! = kolejka mikrozadań
  • zadanie może zostać przeniesione do kolejki makrozadań lub mikrozadań
  • kiedy zadanie jest zepchnięte do kolejki (mikro / makro), mamy na myśli zakończenie przygotowania pracy, więc zadanie można wykonać już teraz.

A model procesu pętli zdarzeń wygląda następująco:

gdy stos wywołań jest pusty, wykonaj następujące czynności:

  1. wybierz najstarsze zadanie (zadanie A) w kolejkach zadań
  2. jeśli zadanie A jest puste (oznacza, że ​​kolejki zadań są puste), przejdź do kroku 6
  3. ustaw „aktualnie uruchomione zadanie” na „zadanie A”
  4. uruchom „zadanie A” (oznacza uruchomienie funkcji wywołania zwrotnego)
  5. ustaw „aktualnie uruchomione zadanie” na null, usuń „zadanie A”
  6. wykonać kolejkę mikrozadań
    • (a). wybierz najstarsze zadanie (zadanie x) w kolejce mikrozadań
    • (b). jeśli zadanie x jest puste (oznacza, że ​​kolejki mikrozadań są puste), przejdź do kroku (g)
    • (c) .set „aktualnie uruchomione zadanie” na „zadanie x”
    • (d) .run "zadanie x"
    • (e). ustaw „aktualnie uruchomione zadanie” na null, usuń „zadanie x”
    • (f). wybierz następne najstarsze zadanie w kolejce mikrozadań, przejdź do kroku (b)
    • (g). zakończenie kolejki mikrozadań;
  7. przejdź do kroku 1.

uproszczony model procesu wygląda następująco:

  1. uruchom najstarsze zadanie w kolejce makrozadań, a następnie je usuń.
  2. uruchom wszystkie dostępne zadania w kolejce mikrozadań, a następnie je usuń.
  3. następna runda: uruchom następne zadanie w kolejce makrozadań (krok przeskoku 2)

coś do zapamiętania:

  1. gdy zadanie (w kolejce makrozadań) jest uruchomione, mogą być rejestrowane nowe zdarzenia, tak więc nowe zadania mogą być tworzone.
    • wywołanie zwrotne promiseA.then () jest zadaniem
      • obietnica A została rozwiązana / odrzucona: zadanie zostanie przeniesione do kolejki mikrozadań w bieżącej rundzie pętli zdarzeń.
      • obietnica A oczekuje: zadanie zostanie przesunięte do kolejki mikrozadań w przyszłej rundzie pętli zdarzeń (może to być następna runda)
    • Funkcja zwrotna setTimeout (callback, n) jest zadaniem i zostanie umieszczona w kolejce makrozadań, nawet n wynosi 0;
  2. zadanie w kolejce mikrozadań zostanie uruchomione w bieżącej rundzie, natomiast zadanie w kolejce makrozadań będzie musiało czekać na kolejną rundę pętli zdarzeń.
  3. wszyscy wiemy, że wywołania zwrotne „click”, „scroll”, „ajax”, „setTimeout” ... to zadania, jednak powinniśmy również pamiętać o kodach js jako całości w tagu script też jest zadaniem (makrozadaniem).
wengeezhang
źródło
2
To świetne wyjaśnienie! Dzięki za udostępnienie !. Jeszcze jedna rzecz, o której warto wspomnieć, znajduje się w NodeJs , setImmediate()to makro / zadanie i process.nextTick()jest to mikro / zadanie.
LeOn - Han Li
6
A co z paintzadaniami przeglądarki ? W jakiej kategorii by pasowali?
Legends
Myślę, że pasowałyby do mikro zadań (takich jak requestAnimationFrame)
Divyanshu Maithani
Oto kolejność uruchamiania pętli zdarzeń w wersji 8 -> stos wywołań || Mikrozadania || Kolejka zadań || rAF || Drzewo renderowania || Układ || Farba || <Natywne wywołania systemu operacyjnego do rysowania pikseli na ekranie> <----- 1) DOM (nowe zmiany), CSSOM (nowe zmiany), drzewo renderowania, układ i malowanie następują po wywołaniu zwrotnym requestAnimationFrame zgodnie z licznikami czasu pętli zdarzeń. Dlatego ważne jest, aby zakończyć swoje operacje DOM przed RAF tak bardzo, jak to tylko możliwe, reszta może iść w RAF. PS: wywołanie rAF spowoduje wykonanie zadania makra.
Anvesh Checka
Nie wiem, czy się mylę, ale trochę nie zgadzam się z tą odpowiedzią, mikrozadania biegną przed makrozadaniem. codepen.io/walox/pen/yLYjNRq ?
walox
9

Myślę, że nie możemy omawiać pętli zdarzeń w oddzieleniu od stosu, więc:

JS ma trzy „stosy”:

  • standardowy stos dla wszystkich wywołań synchronicznych (jedna funkcja wywołuje inną itd.)
  • kolejka mikrozadań (lub kolejka zadań lub stos mikrozadań) dla wszystkich operacji asynchronicznych z wyższym priorytetem (process.nextTick, Promises, Object.observe, MutationObserver)
  • kolejka makrotask (lub kolejka zdarzeń, kolejka zadań, kolejka makrozadań) dla wszystkich operacji asynchronicznych z niższym priorytetem (setTimeout, setInterval, setImmediate, requestAnimationFrame, I / O, renderowanie interfejsu użytkownika)
|=======|
| macro |
| [...] |
|       |
|=======|
| micro |
| [...] |
|       |
|=======|
| stack |
| [...] |
|       |
|=======|

A pętla zdarzeń działa w ten sposób:

  • wykonuj wszystko od dołu do góry ze stosu i TYLKO gdy stos jest pusty sprawdź co się dzieje w kolejkach powyżej
  • sprawdź mikro stos i wykonaj wszystko tam (jeśli jest to wymagane) za pomocą stosu, jedno mikro zadanie po drugim, aż kolejka mikrozadań będzie pusta lub nie będzie wymagała żadnego wykonania, a TYLKO wtedy sprawdź stos makr
  • sprawdź stos makr i wykonaj wszystko tam (jeśli jest to wymagane) za pomocą stosu

Stos Mico nie zostanie dotknięty, jeśli stos nie jest pusty. Stos makr nie zostanie dotknięty, jeśli mikro stos nie jest pusty LUB nie wymaga wykonania.

Podsumowując: kolejka mikrozadań jest prawie taka sama jak kolejka makrozadań, ale te zadania (process.nextTick, Promises, Object.observe, MutationObserver) mają wyższy priorytet niż makrozadania.

Micro jest jak makro, ale ma wyższy priorytet.

Tutaj masz „ostateczny” kod do zrozumienia wszystkiego.

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
*/
user1660210
źródło
1
Nazywanie kolejki stosem jest całkowicie mylące.
Bergi