Разница между функциями 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);
}
);