Saltar al contenido principal




Una arquitectura fuera del hilo principal puede mejorar significativamente la confiabilidad y la experiencia del usuario de su aplicación.

En los últimos 20 años, la web ha evolucionado drásticamente desde documentos estáticos con algunos estilos e imágenes hasta aplicaciones complejas y dinámicas. Sin embargo, una cosa se ha mantenido prácticamente sin cambios: solo tenemos un hilo por pestaña del navegador (con algunas excepciones) para hacer el trabajo de renderizar nuestros sitios y ejecutar nuestro JavaScript.

Como resultado, el hilo principal se ha vuelto increíblemente sobrecargado de trabajo. Y a medida que aumenta la complejidad de las aplicaciones web, el hilo principal se convierte en un cuello de botella significativo para el rendimiento. Para empeorar las cosas, la cantidad de tiempo que lleva ejecutar el código en el hilo principal para un usuario determinado es casi completamente impredecible
porque las capacidades del dispositivo tienen un efecto enorme en el rendimiento. Esa imprevisibilidad solo aumentará a medida que los usuarios accedan a la web desde un conjunto cada vez más diverso de dispositivos, desde teléfonos con funciones hiperrestringidas hasta máquinas insignia de alta potencia y alta frecuencia de actualización.

Si queremos que las aplicaciones web sofisticadas cumplan de manera confiable con las pautas de rendimiento como Modelo RAIL—Que se basa en datos empíricos sobre la percepción humana y la psicología — necesitamos formas de ejecutar nuestro código fuera del hilo principal (OMT).

Si desea saber más sobre el caso de una arquitectura OMT, mire mi charla CDS 2019 a continuación.

Subprocesos con trabajadores web

Las plataformas nativas generalmente admiten el trabajo en paralelo al permitirle asignar una función a un hilo, que se ejecuta en paralelo con el resto de su programa. Puede acceder a las mismas variables desde ambos hilos, y el acceso a estos recursos compartidos se puede sincronizar con mutex y semáforos para evitar condiciones de carrera.

En JavaScript, podemos obtener una funcionalidad similar de los trabajadores web, que existen desde 2007 y son compatibles con los principales navegadores desde 2012. Los trabajadores web se ejecutan en paralelo con el hilo principal, pero a diferencia del hilo nativo, no pueden compartir variables.

No confunda a los trabajadores web con los trabajadores de servicios o worklets. Si bien los nombres son similares, la funcionalidad y los usos son diferentes.

Para crear un trabajador web, pase un archivo al constructor del trabajador, que comienza a ejecutar ese archivo en un hilo separado:

const worker = new Worker("./worker.js");

Comuníquese con el trabajador web enviando mensajes a través del
postMessage API. Pase el valor del mensaje como parámetro en el postMessage llamar y luego agregar un oyente de eventos de mensaje al trabajador:

main.js

const worker = new Worker("./worker.js");
worker.postMessage([40, 2]);

worker.js

addEventListener("message", event => {
const [a, b] = event.data;
});

Para enviar un mensaje al hilo principal, use el mismo postMessage API en el trabajador web y configure un detector de eventos en el hilo principal:

main.js

const worker = new Worker("./worker.js");
worker.postMessage([40, 2]);
worker.addEventListener("message", event => {
console.log(event.data);
});

worker.js

addEventListener("message", event => {
const [a, b] = event.data;

postMessage(a+b);
});

Es cierto que este enfoque es algo limitado. Históricamente, los trabajadores web se han utilizado principalmente para sacar una sola pieza de trabajo pesado del hilo principal. Tratar de manejar múltiples operaciones con un solo trabajador web se vuelve difícil de manejar rápidamente: debe codificar no solo los parámetros, sino también la operación en el mensaje, y debe llevar la contabilidad para hacer coincidir las respuestas con las solicitudes. Esa complejidad es probablemente la razón por la que los trabajadores web no han sido adoptados más ampliamente.

Pero si pudiéramos eliminar algunas de las dificultades de comunicación entre el hilo principal y los trabajadores web, este modelo podría ser ideal para muchos casos de uso. Y, afortunadamente, ¡hay una biblioteca que hace precisamente eso!

Comlink: hacer que los trabajadores web trabajen menos

Comlink es una biblioteca cuyo objetivo es permitirle utilizar trabajadores web sin tener que pensar en los detalles de postMessage. Comlink le permite compartir variables entre los trabajadores web y el hilo principal casi como lenguajes de programación que soportan de forma nativa el hilo.

Usted configura Comlink importándolo en un trabajador web y definiendo un conjunto de funciones para exponer al hilo principal. Luego importa Comlink en el hilo principal, envuelve el trabajador y obtiene acceso a las funciones expuestas:

worker.js

import {expose} from "comlink";

const api = {
someMethod() { }
}
expose(api);

main.js

import {wrap} from "comlink";

const worker = new Worker("./worker.js");
const api = wrap(worker);

los api La variable en el hilo principal se comporta igual que la del trabajador web, excepto que cada función devuelve una promesa de un valor en lugar del valor en sí.

¿Qué código debería transferir a un trabajador web?

Los trabajadores web no tienen acceso al DOM y muchas API como WebUSB,
WebRTCo
Audio Web, por lo que no puede colocar partes de su aplicación que dependan de dicho acceso en un trabajador. Aún así, cada pequeño fragmento de código que se traslada a un trabajador genera más espacio para la cabeza en el hilo principal para las cosas que tiene estar allí, como actualizar la interfaz de usuario.

Restringir el acceso a la interfaz de usuario al hilo principal es algo típico en otros idiomas. De hecho, tanto iOS como Android llaman al hilo principal el Hilo de interfaz de usuario.

Un problema para los desarrolladores web es que la mayoría de las aplicaciones web se basan en un marco de interfaz de usuario como Vue o React para orquestar todo en la aplicación; todo es un componente del marco y, por lo tanto, está inherentemente vinculado al DOM. Eso parecería dificultar la migración a una arquitectura OMT.

Sin embargo, si cambiamos a un modelo en el que las preocupaciones de la interfaz de usuario se separan de otras preocupaciones, como la administración del estado, los trabajadores web pueden ser bastante útiles incluso con aplicaciones basadas en marcos. Ese es exactamente el enfoque adoptado con PROXX.

PROXX: un caso de estudio OMT

El equipo de Google Chrome desarrolló PROXX como un clon de Buscaminas que cumple
Aplicación web progresiva requisitos, incluido trabajar sin conexión y tener una experiencia de usuario atractiva. Desafortunadamente, las primeras versiones del juego funcionaron mal en dispositivos restringidos como los teléfonos con funciones, lo que llevó al equipo a darse cuenta de que el hilo principal era un cuello de botella.

El equipo decidió utilizar trabajadores web para separar el estado visual del juego de su lógica:

  • El hilo principal maneja la representación de animaciones y transiciones.
  • Un trabajador web maneja la lógica del juego, que es puramente computacional.

Este enfoque es similar al Redux
Patrón de flujo, muchas aplicaciones de Flux pueden migrar con bastante facilidad a una arquitectura OMT. Echa un vistazo a esta publicación de blog
para leer más sobre cómo aplicar OMT a una aplicación Redux.

OMT tuvo efectos interesantes en el rendimiento del teléfono con funciones de PROXX. En la versión que no es OMT, la interfaz de usuario se congela durante seis segundos después de que el usuario interactúa con ella. No hay comentarios y el usuario tiene que esperar los seis segundos completos antes de poder hacer otra cosa.

El tiempo de respuesta de la IU en el no OMT versión de PROXX.

En la versión OMT, sin embargo, el juego toma doce segundos para completar una actualización de la interfaz de usuario. Si bien eso parece una pérdida de rendimiento, en realidad conduce a una mayor retroalimentación para el usuario. La ralentización se produce porque la aplicación envía más marcos que la versión que no es OMT, que no envía ningún marco. Por lo tanto, el usuario sabe que algo está sucediendo y puede continuar jugando a medida que se actualiza la interfaz de usuario, lo que hace que el juego se sienta considerablemente mejor.

El tiempo de respuesta de la IU en el OMT versión de PROXX.

Esta es una compensación consciente: brindamos a los usuarios de dispositivos restringidos una experiencia que siente mejor sin penalizar a los usuarios de dispositivos de gama alta.

Implicaciones de una arquitectura OMT

Como muestra el ejemplo de PROXX, OMT hace que su aplicación se ejecute de manera confiable en una gama más amplia de dispositivos, pero no hace que su aplicación sea más rápida:

  • Simplemente está moviendo el trabajo del hilo principal, no reduciendo el trabajo.
  • La sobrecarga de comunicación adicional entre el trabajador web y el hilo principal a veces puede hacer que las cosas sean un poco más lentas.

Considerando las compensaciones

Dado que el hilo principal es libre de procesar las interacciones del usuario, como desplazarse mientras se ejecuta JavaScript, hay menos fotogramas descartados, aunque el tiempo de espera total puede ser ligeramente más largo. Hacer que el usuario espere un poco es preferible a descartar un fotograma porque el margen de error es menor para los fotogramas descartados: el descarte de un fotograma ocurre en milisegundos, mientras cientos de milisegundos antes de que un usuario perciba el tiempo de espera.

Debido a la imprevisibilidad del rendimiento en todos los dispositivos, el objetivo de la arquitectura OMT se trata realmente de reduciendo el riesgo—Hacer que su aplicación sea más robusta frente a condiciones de tiempo de ejecución altamente variables— no sobre los beneficios de rendimiento de la paralelización. El aumento de la capacidad de recuperación y las mejoras de UX valen la pena cualquier pequeña compensación en la velocidad.

A los desarrolladores a veces les preocupa el costo de copiar objetos complejos en el hilo principal y los trabajadores web. Hay más detalles en la charla, pero, en general, no debe romper su presupuesto de rendimiento si la representación JSON en cadena de su objeto es inferior a 10 KB. Si necesita copiar objetos más grandes, considere usar
ArrayBuffer
o WebAssembly. Puede leer más sobre este problema en
esta publicación de blog sobre postMessage actuación.

Una nota sobre herramientas

Los trabajadores web aún no son convencionales, por lo que la mayoría de las herramientas de módulos, como WebPack
y Enrollar—No los apoye desde el primer momento. (Paquete o empaquetar ¡aunque sí!) Afortunadamente, hay complementos para hacer que los trabajadores web, bueno, trabajo con WebPack y Rollup:

Resumiendo

Para asegurarnos de que nuestras aplicaciones sean tan confiables y accesibles como sea posible, especialmente en un mercado cada vez más globalizado, necesitamos admitir dispositivos restringidos; es la forma en que la mayoría de los usuarios acceden a la web a nivel mundial. OMT ofrece una forma prometedora de aumentar el rendimiento en dichos dispositivos sin afectar negativamente a los usuarios de dispositivos de gama alta.

Además, OMT tiene beneficios secundarios:

Los trabajadores web no tienen por qué dar miedo. Herramientas como Comlink eliminan el trabajo de los trabajadores y los convierten en una opción viable para una amplia gama de aplicaciones web.

R Marketing Digital