Sự khác biệt giữa microtask và macrotask trong bối cảnh vòng lặp sự kiện


139

Tôi vừa đọc xong thông số kỹ thuật Promise / A + và tình cờ thấy các thuật ngữ microtask và macrotask: xem http://promisesaplus.com/#notes

Tôi chưa bao giờ nghe về các điều khoản này trước đây, và bây giờ tôi tò mò sự khác biệt có thể là gì?

Tôi đã cố gắng tìm một số thông tin trên web, nhưng tất cả những gì tôi tìm thấy là bài đăng này từ Lưu trữ w3.org (không giải thích sự khác biệt với tôi): http://lists.w3.org/Archives /Public/public-nextweb/2013Jul/0018.html

Ngoài ra, tôi đã tìm thấy một mô-đun npm có tên là "macrotask": https://www.npmjs.org/package/macrotask Một lần nữa, không rõ chính xác sự khác biệt là gì.

Tất cả những gì tôi biết là, nó có liên quan đến vòng lặp sự kiện, như được mô tả trong https://html.spec.whatwg.org/multipage/webappapis.html#task-queuehttps: //html.spec.whatwg .org / bội / webappapis.html # biểu diễn-a-microtask-điểm kiểm tra

Tôi biết rằng về mặt lý thuyết tôi có thể tự rút ra những khác biệt, dựa trên thông số WHATWG này. Nhưng tôi chắc chắn rằng những người khác cũng có thể được hưởng lợi từ một lời giải thích ngắn được đưa ra bởi một chuyên gia.


Tóm lại: nhiều hàng đợi sự kiện lồng nhau. Bạn thậm chí có thể tự thực hiện một:while (task = todo.shift()) task();
Bergi

1
Dành cho ai đó muốn biết thêm một chút chi tiết: Bí mật về Ninja JavaScript, Phiên bản 2, CHƯƠNG 13 Sự kiện sống sót
Ethan

Câu trả lời:


217

Một vòng lặp của vòng lặp sự kiện sẽ có chính xác một tác vụ được xử lý từ hàng đợi macrotask (hàng đợi này được gọi đơn giản là hàng đợi nhiệm vụ trong đặc tả WHATWG ). Sau khi macrotask này kết thúc, tất cả các microt Nhiệm vụ có sẵn sẽ được xử lý, cụ thể là trong cùng một chu kỳ. Trong khi các microt Nhiệm vụ này được xử lý, chúng có thể xếp hàng thậm chí nhiều microt Nhiệm vụ hơn, tất cả sẽ được chạy từng cái một, cho đến khi hàng đợi microtask hết.

Hậu quả thực tế của việc này là gì?

Nếu một microtask đệ quy hàng đợi các microt Nhiệm vụ khác, có thể sẽ mất nhiều thời gian cho đến khi macrotask tiếp theo được xử lý. Điều này có nghĩa là, bạn có thể kết thúc với một giao diện người dùng bị chặn hoặc một số I / O đã hoàn thành trong ứng dụng của bạn.

Tuy nhiên, ít nhất là liên quan đến hàm process.nextTick của Node.js (xếp hàng microt Nhiệm vụ ), có một bảo vệ sẵn có để chống lại việc chặn như vậy bằng phương thức process.maxTickDepth. Giá trị này được đặt thành mặc định là 1000, cắt giảm quá trình xử lý microt Nhiệm vụ tiếp theo sau khi đạt đến giới hạn này cho phép xử lý macrotask tiếp theo )

Vậy khi nào nên dùng gì?

Về cơ bản, sử dụng microt Nhiệm vụ khi bạn cần thực hiện công cụ không đồng bộ theo cách đồng bộ (nghĩa là khi bạn nói sẽ thực hiện nhiệm vụ (vi mô) này trong tương lai gần nhất ). Nếu không, dính vào macrot Nhiệm vụ .

Ví dụ

macrotasks: setTimeout , setInterval , setImmediate , requestAnimationFrame , I / O , giao diện người dùng render
microtasks: process.nextTick , Promises , queueMicrotask , MutationObserver


4
Mặc dù có một điểm kiểm tra microtask trong vòng lặp sự kiện, nhưng đây không phải là nơi mà hầu hết các nhà phát triển sẽ gặp phải microt Nhiệm vụ. Các microt Nhiệm vụ được xử lý khi ngăn xếp JS trống. Điều này có thể xảy ra nhiều lần trong một tác vụ hoặc thậm chí trong các bước kết xuất của vòng lặp sự kiện.
JaffaTheCake

2
process.maxTickDepthđã bị xóa rất lâu trước đây: github.com/nodejs/node/blob/
mẹo

bạn cũng có thể sử dụng phương thức queueMicrotask () để thêm microtask mới
Zoom ALL

Cảm ơn @Zoom ALL, cho đến bây giờ vẫn chưa biết queueMicrotask (). Tôi đã thêm nó vào câu trả lời cộng với các liên kết đến tất cả mọi thứ ...
NicBright

requestAnimationFrame (rAF) không chỉ tạo ra các vi lệnh. Nói chung, cuộc gọi rAF tạo ra một hàng đợi riêng
Zoom ALL

67

Các khái niệm cơ bản trong spec :

  • Một vòng lặp sự kiện có một hoặc nhiều hàng đợi nhiệm vụ. (Hàng đợi nhiệm vụ là hàng đợi macrotask)
  • Mỗi vòng lặp sự kiện có một hàng đợi microtask.
  • hàng đợi nhiệm vụ = hàng đợi macrotask! = hàng đợi microtask
  • một tác vụ có thể được đẩy vào hàng đợi macrotask hoặc hàng đợi microtask
  • khi một tác vụ được đẩy vào hàng đợi (micro / macro), chúng tôi có nghĩa là việc chuẩn bị công việc đã kết thúc, vì vậy nhiệm vụ có thể được thực hiện ngay bây giờ.

Và mô hình quy trình vòng lặp sự kiện như sau:

khi ngăn xếp cuộc gọi trống, hãy thực hiện các bước-

  1. chọn tác vụ cũ nhất (tác vụ A) trong hàng đợi tác vụ
  2. nếu tác vụ A là null (có nghĩa là hàng đợi tác vụ trống), chuyển sang bước 6
  3. đặt "nhiệm vụ hiện đang chạy" thành "nhiệm vụ A"
  4. chạy "tác vụ A" (có nghĩa là chạy chức năng gọi lại)
  5. đặt "tác vụ hiện đang chạy" thành null, xóa "tác vụ A"
  6. thực hiện hàng đợi microtask
    • (a). Chọn tác vụ cũ nhất (tác vụ x) trong hàng đợi microtask
    • (b) .if tác vụ x là null (có nghĩa là hàng đợi microtask trống), nhảy đến bước (g)
    • (c) .set "hiện đang chạy tác vụ" thành "tác vụ x"
    • (d) .sl "nhiệm vụ x"
    • (e) .set "hiện đang chạy tác vụ" thành null, xóa "tác vụ x"
    • (f). Chọn nhiệm vụ cũ nhất tiếp theo trong hàng đợi microtask, chuyển sang bước (b)
    • (g). Hoàn thành hàng đợi microtask;
  7. nhảy sang bước 1

một mô hình quy trình đơn giản hóa như sau:

  1. chạy tác vụ cũ nhất trong hàng đợi macrotask, sau đó loại bỏ nó.
  2. chạy tất cả các tác vụ có sẵn trong hàng đợi microtask, sau đó loại bỏ chúng.
  3. vòng tiếp theo: chạy nhiệm vụ tiếp theo trong hàng đợi macrotask (bước nhảy 2)

một cái gì đó để nhớ:

  1. khi một tác vụ (trong hàng đợi macrotask) đang chạy, các sự kiện mới có thể được đăng ký. Vì vậy, các tác vụ mới có thể được tạo. Dưới đây là hai tác vụ được tạo mới:
    • Cuộc gọi lại của PromA.then () là một nhiệm vụ
      • lời hứa được giải quyết / từ chối: nhiệm vụ sẽ được đẩy vào hàng đợi microtask trong vòng lặp sự kiện hiện tại.
      • Promise đang chờ xử lý: tác vụ sẽ được đẩy vào hàng đợi microtask trong vòng lặp sự kiện trong tương lai (có thể là vòng tiếp theo)
    • setTimeout (gọi lại, gọi lại của n) là một nhiệm vụ và sẽ được đẩy vào hàng đợi macrotask, thậm chí n là 0;
  2. nhiệm vụ trong hàng đợi microtask sẽ được chạy trong vòng hiện tại, trong khi nhiệm vụ trong hàng đợi macrotask phải chờ vòng tiếp theo của vòng lặp sự kiện.
  3. tất cả chúng ta đều biết gọi lại của "nhấp chuột", "cuộn", "ajax", "setTimeout" ... là các tác vụ, tuy nhiên chúng ta cũng nên nhớ mã js vì toàn bộ thẻ script cũng là một tác vụ (macrotask).

2
Đây là lời giải thích tuyệt vời! Cám ơn vì đã chia sẻ!. Một điều nữa cần đề cập là trong NodeJs , setImmediate()là macro / task và process.nextTick()là một micro / job.
LeOn - Han Li

6
Còn paintnhiệm vụ trình duyệt thì sao? Họ sẽ phù hợp với thể loại nào?
Huyền thoại

Tôi nghĩ rằng chúng sẽ phù hợp với các nhiệm vụ vi mô (như requestAnimationFrame)
Divyanshu Maithani

Đây là thứ tự mà vòng lặp sự kiện v8 chạy -> Call Stack | | Nhiệm vụ vi mô | | Hàng đợi nhiệm vụ | | Hoa Kỳ | | Kết xuất cây | | Bố cục | | Sơn | | <OS Native gọi để vẽ các pixel trên màn hình> <----- 1) DOM (thay đổi mới), CSSOM (thay đổi mới), kết xuất cây, bố cục và vẽ xảy ra sau khi gọi lại requestAnimationFrame theo bộ định thời vòng lặp sự kiện. Đây là lý do tại sao điều quan trọng là phải hoàn thành các hoạt động DOM của bạn trước rAF càng nhiều càng tốt, phần còn lại có thể đi trong rAF. PS: gọi rAF sẽ kích hoạt thực thi tác vụ macro.
Anvesh Checka

Tôi không biết mình có nhầm không nhưng tôi không đồng ý với câu trả lời này, microt Nhiệm vụ chạy trước macrotask. codepen.io/walox/pen/yLYjNRq ?
walox

9

Tôi nghĩ rằng chúng ta không thể thảo luận về vòng lặp sự kiện tách khỏi ngăn xếp, vì vậy:

JS có ba "ngăn xếp":

  • ngăn xếp tiêu chuẩn cho tất cả các cuộc gọi đồng bộ (một chức năng gọi một chức năng khác, v.v.)
  • hàng đợi microtask (hoặc hàng đợi công việc hoặc ngăn xếp microtask) cho tất cả các hoạt động không đồng bộ với mức độ ưu tiên cao hơn (process.nextTick, Promising, Object.observe, MutingObserver)
  • hàng đợi macrotask (hoặc hàng đợi sự kiện, hàng đợi nhiệm vụ, hàng đợi macrotask) cho tất cả các hoạt động không đồng bộ với mức độ ưu tiên thấp hơn (setTimeout, setInterval, setImmediate, requestAnimationFrame, I / O, kết xuất giao diện người dùng)
|=======|
| macro |
| [...] |
|       |
|=======|
| micro |
| [...] |
|       |
|=======|
| stack |
| [...] |
|       |
|=======|

Và vòng lặp sự kiện hoạt động theo cách này:

  • thực hiện mọi thứ từ dưới lên trên từ ngăn xếp và CHỈ khi ngăn xếp trống, kiểm tra xem điều gì đang xảy ra trong hàng đợi ở trên
  • kiểm tra ngăn xếp vi mô và thực hiện mọi thứ ở đó (nếu cần) với sự trợ giúp của ngăn xếp, hết nhiệm vụ này đến lần khác cho đến khi hàng đợi microtask trống hoặc không yêu cầu bất kỳ thực thi nào và CHỈ sau đó kiểm tra ngăn xếp macro
  • kiểm tra ngăn xếp macro và thực hiện mọi thứ ở đó (nếu cần) với sự trợ giúp của ngăn xếp

Ngăn xếp mico sẽ không được chạm nếu ngăn xếp không trống. Ngăn xếp macro sẽ không được chạm nếu ngăn xếp vi mô không trống HOẶC không yêu cầu thực hiện.

Tóm lại: hàng đợi microtask gần giống như hàng đợi macrotask nhưng các tác vụ đó (process.nextTick, Promising, Object.observe, MutingObserver) có mức độ ưu tiên cao hơn macrot Nhiệm vụ.

Micro giống như macro nhưng với mức độ ưu tiên cao hơn.

Ở đây bạn có mã "cuối cùng" để hiểu mọi thứ.

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
Gọi một hàng đợi một ngăn xếp là hoàn toàn khó hiểu.
Bergi
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.