Разница между функциями async для Javascript и веб-рабочими?
Поистине, какая разница между веб-работниками и функциями, объявленными как
async function xxx()
{
}
?
Я знаю, что веб-рабочие выполняются на отдельных потоках, но как насчет асинхронных функций? Связаны ли такие функции с потоком так же, как функция, выполняемая через setInterval, или они подвержены еще одному другому потоку?
Ответы
Ответ 1
Async
функции являются просто синтаксическим сахаром вокруг Promises
и являются обертками для Callbacks
. Поэтому в основном, когда вы await
что - то JS-движок продолжает с другими вещами, вплоть до callback
вас ждут звонков обратно.
Участие другого потока зависит от того, что вы ожидаете в async
функции. Если это таймер (setTimeout
), устанавливается внутренний таймер, и JS-поток продолжает выполнять другие действия, пока не завершится таймер, а затем продолжит выполнение.
Это поведение примерно одинаково с каждой функцией, принимающей обратный вызов или возвращающей promise
. Однако некоторые из них, особенно в среде Node.js(fetch
, fs.readFile
), будут внутренне запускать другой поток. Вы только передаете некоторые аргументы и получаете результаты, когда поток завершен. Однако с помощью WebWorkers
вы напрямую управляете другим потоком. Для уверенности вы также можете await
действий от этого другого потока:
const workerDone = new Promise(res => window.onmessage = res);
(async function(){
const result = await workerDone;
//...
})();
TL; DR:
JS <---> callbacks / promises <--> internal Thread / Webworker
Ответ 2
В отличие от WebWorkers
, async
функции никогда не гарантированно выполняются в отдельном потоке.
Они просто не блокируют весь поток, пока не придет их ответ. Вы можете думать о них, как о том, что они зарегистрированы как ожидающие результата, позволяющие выполнять другой код, и когда их ответ поступает, они выполняются; отсюда и название асинхронного программирования.
Это достигается с помощью очереди сообщений, которая представляет собой список сообщений, подлежащих обработке. Каждое сообщение имеет связанную функцию, которая вызывается для обработки сообщения.
Делая это:
setTimeout(() => {
console.log('foo')
}, 1000)
просто добавит функцию обратного вызова (которая ведет на консоль) в очередь сообщений. Когда истекает таймер 1000 мс, сообщение извлекается из очереди сообщений и выполняется.
Пока таймер тикает, другой код может свободно выполняться. Это то, что дает иллюзию многопоточности.
В приведенном выше примере setTimeout
используются обратные вызовы. Promises
и async
работают одинаково на более низком уровне - они используют эту концепцию очереди сообщений, но синтаксически отличаются друг от друга.
Ответ 3
Асинхронные функции не имеют ничего общего с веб-рабочими или дочерними дочерними процессами - в отличие от них, они не являются решением для параллельной обработки нескольких потоков.
async function
- это всего лишь 1 синтаксический сахар для функции, возвращающей обещание, then()
.
async function example() {
await delay(1000);
console.log("waited.");
}
это то же самое, что и
function example() {
return Promise.resolve(delay(1000)).then(() => {
console.log("waited.");
});
}
Эти два практически неразличимы в своем поведении. Семантика await
или заданная с точки зрения обещаний, и каждая async function
возвращает обещание ее результата.
1: Синтаксический сахар становится более сложным в присутствии контрольных структур, таких как if
/else
или циклы, которые намного сложнее выразить в виде линейной цепочки обещаний, но все же концептуально то же самое.
Связаны ли такие функции с потоком так же, как функция, выполняемая через setInterval
?
Да, асинхронные части async function
выполняются как (обещающие) обратные вызовы в стандартном цикле событий. delay
в приведенном выше примере была бы реализована с обычным setTimeout
- завернутым в обещание для легкого потребления:
function delay(t) {
return new Promise(resolve => {
setTimeout(resolve, t);
});
}
Ответ 4
Рабочие также получают доступ к асинхронному коду (то есть к обещаниям), однако Workers - это решение задач интенсивного процессора, которые блокируют поток, на котором запускается JS-код; даже если эта интенсивная функция CPU вызывается асинхронно.
Поэтому, если у вас есть интенсивная работа с процессором, например renderThread(duration)
и если вам нравится
new Promise((v,x) => setTimeout(_ => (renderThread(500), v(1)),0)
.then(v => console.log(v);
new Promise((v,x) => setTimeout(_ => (renderThread(100), v(2)),0)
.then(v => console.log(v);
Даже если второй занимает меньше времени для завершения, он будет вызываться только после того, как первый выпустит поток ЦП. Итак, мы получим первый 1
а затем 2
на консоли.
Однако, если бы эти две функции выполнялись на отдельных Рабочих, то ожидаемый результат был бы равен 2
и 1
как тогда они могли бы работать одновременно, а вторая заканчивалась и возвращала сообщение раньше.
Таким образом, для базовых операций ввода-вывода стандартный однопоточный асинхронный код очень эффективен, и потребность в Рабочих возникает из-за необходимости использования задач, которые интенсивно работают в ЦП и могут быть сегментированы (назначены сразу нескольким Рабочим), таких как БПФ и еще много чего.
Ответ 5
Я хочу добавить свой собственный ответ на мой вопрос, с пониманием, которое я собрал во всех остальных ответах:
В конечном счете, все, кроме веб-работников, являются прославленными обратными вызовами. Код в асинхронных функциях, функции, вызываемые через обещания, функции, вызываемые через setInterval и такие - все выполняются в основном потоке с механизмом, сходным с переключением контекста. Никакой параллелизм не существует вообще.
Истинное параллельное исполнение со всеми его преимуществами и ловушками относится только к веб-работникам и веб-работникам.
(жаль - я подумал, что с "асинхронными функциями" мы наконец получили упорядоченные и "встроенные" потоки)
Ответ 6
Вот способ вызова стандартных функций в качестве рабочих, что позволяет использовать истинный параллелизм. Это нечестивый взлом, написанный в крови с помощью сатаны, и, вероятно, есть тонна призраков браузера, которые могут его сломать, но насколько я могу сказать, это работает.
[ constraints: заголовок функции должен быть таким же простым, как функция f (a, b, c), и если есть какой-либо результат, он должен пройти через оператор return]
function Async(func, params, callback)
{
// ACQUIRE ORIGINAL FUNCTION CODE
var text = func.toString();
// EXTRACT ARGUMENTS
var args = text.slice(text.indexOf("(") + 1, text.indexOf(")"));
args = args.split(",");
for(arg of args) arg = arg.trim();
// ALTER FUNCTION CODE:
// 1) DECLARE ARGUMENTS AS VARIABLES
// 2) REPLACE RETURN STATEMENTS WITH THREAD POSTMESSAGE AND TERMINATION
var body = text.slice(text.indexOf("{") + 1, text.lastIndexOf("}"));
for(var i = 0, c = params.length; i<c; i++) body = "var " + args[i] + " = " + JSON.stringify(params[i]) + ";" + body;
body = body + " self.close();";
body = body.replace(/return\s+([^;]*);/g, 'self.postMessage($1); self.close();');
// CREATE THE WORKER FROM FUNCTION ALTERED CODE
var code = URL.createObjectURL(new Blob([body], {type:"text/javascript"}));
var thread = new Worker(code);
// WHEN THE WORKER SENDS BACK A RESULT, CALLBACK AND TERMINATE THE THREAD
thread.onmessage =
function(result)
{
if(callback) callback(result.data);
thread.terminate();
}
}
Итак, предположим, что у вас есть эта потенциально интенсивная функция процессора...
function HeavyWorkload(nx, ny)
{
var data = [];
for(var x = 0; x < nx; x++)
{
data[x] = [];
for(var y = 0; y < ny; y++)
{
data[x][y] = Math.random();
}
}
return data;
}
... теперь вы можете назвать это следующим образом:
Async(HeavyWorkload, [1000, 1000],
function(result)
{
console.log(result);
}
);