Una arquitectura fuera del hilo principal puede mejorar significativamente la confiabilidad y la experiencia del Nom d'utilisateur de su aplicación.
En los últimos 20 años, la la toile 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 le navigateur (con algunas excepciones) para hacer el trabajo de renderizar nuestros sitios y ejecutar nuestro JavaScript.
En conséquence, le fil principal est devenu incroyablement surchargé. Et à mesure que la complexité des applications Web augmente, le thread principal devient un goulot d'étranglement important pour les performances. Pour aggraver les choses, le temps nécessaire pour exécuter le code dans le thread principal pour un utilisateur donné est presque complètement imprévisible
car les capacités de l'appareil ont un effet énorme sur les performances. Cette imprévisibilité ne fera qu'augmenter à mesure que les utilisateurs accèdent au Web à partir d'un ensemble d'appareils de plus en plus diversifiés, des téléphones dotés de fonctionnalités hyper-restreintes aux machines phares à haute puissance et à taux de rafraîchissement élevé.
Si nous voulons que les applications Web sophistiquées respectent de manière fiable les directives de performance telles que Modèle RAIL—Ce qui est basé sur des données empiriques sur la perception humaine et la psychologie - nous avons besoin de moyens pour exécuter notre code filetage principal extérieur (OMT).
Si vous voulez en savoir plus sur le cas d'une architecture OMT, regardez mon exposé CDS 2019 ci-dessous.
Threads avec les web workers
Les plates-formes natives prennent généralement en charge le travail parallèle en vous permettant d'attribuer une fonction à un thread, qui s'exécute en parallèle avec le reste de votre programme. Vous pouvez accéder aux mêmes variables à partir des deux threads, et l'accès à ces partages peut être synchronisé avec mutex et sémaphores pour éviter les conditions de concurrence.
En JavaScript, podemos obtener una funcionalidad similar de los trabajadores web, que existen desde 2007 y son compatibles con los principales navigateurs desde 2012. Los trabajadores web se ejecutan en paralelo con el hilo principal, pero a diferencia del hilo nativo, no pueden compartir variables.
Ne confondez pas les web workers avec les techniciens de service ou worklets. Bien que les noms soient similaires, les fonctionnalités et les utilisations sont différentes.
Pour créer un travailleur Web, transmettez un fichier au constructeur du travailleur, qui commence à exécuter ce fichier dans un thread distinct:
const ouvrier = new Ouvrier("./worker.js");
Communiquez avec le travailleur Web en envoyant des messages via le
postMessage
API. Passez la valeur du message en tant que paramètre dans le postMessage
appelez, puis ajoutez un écouteur d'événement de message au travailleur:
main.js
const ouvrier = new Ouvrier("./worker.js");
ouvrier.postMessage([40, 2]);
worker.js
addEventListener("message", un événement => {
const [à, b] = un événement.Les données;
});
Pour envoyer un message au fil de discussion principal, utilisez le même postMessage
API en el trabajador web y configure un detector de eventos en el hilo principal:
main.js
const ouvrier = new Ouvrier("./worker.js");
ouvrier.postMessage([40, 2]);
ouvrier.addEventListener("message", un événement => {
console.Journal(un événement.Les données);
});
worker.js
addEventListener("message", un événement => {
const [à, b] = un événement.Les données;
postMessage(à+b);
});
Il est vrai que cette approche est quelque peu limitée. Historiquement, les web workers ont été principalement utilisés pour retirer une seule pièce résistante du fil principal. Essayer de gérer plusieurs opérations avec un seul travailleur Web devient rapidement compliqué - vous devez coder non seulement les paramètres, mais également l'opération dans le message, et vous devez faire la comptabilité pour faire correspondre les réponses aux demandes. Cette complexité est probablement la raison pour laquelle les web workers n'ont pas été plus largement adoptés.
Mais si nous pouvions éliminer certaines des difficultés de communication entre le thread principal et les web workers, ce modèle pourrait être idéal pour de nombreux cas d'utilisation. Et heureusement, il existe une bibliothèque qui fait exactement cela!
Comlink: réduire le travail des web workers
Comlink es una biblioteca cuyo objectif 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 programmation que soportan de forma nativa el hilo.
Vous configurez Comlink en l'important dans un web worker et en définissant un ensemble de fonctions à exposer au thread principal. Ensuite, vous importez Comlink dans le thread principal, enveloppez le worker et accédez aux fonctions exposées:
worker.js
importer {expose} desde "comlink";const api = {
someMethod() { }
}
expose(api);
main.js
importer {wrap} desde "comlink";const ouvrier = new Ouvrier("./worker.js");
const api = wrap(ouvrier);
Les api
La variable du thread principal se comporte de la même manière que celle du web worker, sauf que chaque fonction renvoie une promesse d'une valeur au lieu de la valeur elle-même.
Quel code dois-je transférer à un web worker?
Les web workers n'ont pas accès au DOM et à de nombreuses API comme WebUSB,
WebRTCou
Audio Web, vous ne pouvez donc pas placer les parties de votre application qui dépendent d'un tel accès sur un worker. Pourtant, chaque petit morceau de code qui est passé à un worker génère plus de marge dans le thread principal pour les choses qui avoir estar allí, como actualizar la interface utilisateur.
Restreindre l'accès à l'interface utilisateur au thread principal est typique dans d'autres langues. En fait, iOS et Android appellent le thread principal le Fil de l'interface utilisateur.
Un problème pour les développeurs Web est que la plupart des applications Web reposent sur un cadre d'interface utilisateur comme Vue ou React pour orchestrer tout dans l'application; tout est un composant du framework et donc intrinsèquement lié au DOM. Cela semble rendre difficile la migration vers une architecture OMT.
Cependant, si nous passons à un modèle où les préoccupations d'interface utilisateur sont séparées des autres préoccupations, telles que la gestion des états, les travailleurs Web peuvent être très utiles même avec des applications basées sur un framework. C'est exactement l'approche adoptée avec PROXX.
PROXX: une étude de cas OMT
El equipo de Google Chrome desarrolló PROXX como un clon de Buscaminas que cumple
Application Web progressive requisitos, incluido trabajar sin conexión y tener una expérience utilisateur 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.
L'équipe a décidé d'utiliser des web workers pour séparer l'état visuel du jeu de sa logique:
- Le thread principal gère le rendu des animations et des transitions.
- Un web worker gère la logique du jeu, qui est purement informatique.
Cette approche est similaire à Redux
Schéma d'écoulement, de nombreuses applications Flux peuvent migrer assez facilement vers une architecture OMT. Jette un coup d'œil à ce billet de blog
pour en savoir plus sur la façon d'appliquer OMT à une application Redux.
OMT a eu des effets intéressants sur les performances du téléphone fonctionnel PROXX. Dans la version non-OMT, l'interface utilisateur se fige pendant six secondes après que l'utilisateur interagit avec elle. Il n'y a pas de commentaires et l'utilisateur doit attendre les six secondes complètes avant de pouvoir faire autre chose.
Le temps de réponse de l'interface utilisateur dans le pas OMT Version PROXX.
Dans la version OMT, cependant, le jeu prend Douze secondes pour terminer une mise à jour de l'interface utilisateur. Bien que cela semble être une perte de performances, cela entraîne en fait plus de commentaires des utilisateurs. Le ralentissement se produit car l'application envoie plus de trames que la version non OMT, qui n'envoie aucune trame. Par conséquent, l'utilisateur sait que quelque chose se passe et peut continuer à jouer au fur et à mesure que l'interface utilisateur est mise à jour, ce qui améliore considérablement le jeu.
Le temps de réponse de l'interface utilisateur dans le OMT Version PROXX.
Il s'agit d'un compromis conscient: nous offrons aux utilisateurs d'appareils restreints une expérience qui Ressentir mieux sans pénaliser les utilisateurs d'appareils haut de gamme.
Implications d'une architecture 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 être más rápida:
- Vous déplacez simplement le travail du fil principal, sans réduire le travail.
- La surcharge de communication supplémentaire entre le web worker et le thread principal peut parfois ralentir un peu les choses.
Compte tenu des compromis
Étant donné que le thread principal est libre de traiter les interactions des utilisateurs, telles que le défilement pendant l'exécution de JavaScript, il y a moins d'images perdues, bien que le temps d'attente total puisse être légèrement plus long. Faire patienter un peu est préférable à la suppression d'une trame car la marge d'erreur est moindre pour les trames ignorées: la suppression d'une trame se produit en millisecondes, tandis que des centaines millisecondes avant qu'un utilisateur perçoive le délai.
En raison de l'imprévisibilité des performances sur tous les appareils, l'objectif de l'architecture OMT est vraiment réduire le risque- Rendre votre application plus robuste face à des conditions d’exécution très variables, et non sur les avantages de la parallélisation en termes de performances. La récupérabilité accrue et les améliorations UX valent tout petit compromis en termes de vitesse.
Les développeurs sont parfois préoccupés par le coût de la copie d'objets complexes sur le thread principal et les travailleurs Web. Plus de détails sont dans la discussion, mais en général, vous ne devriez pas casser votre budget de performances si la représentation sous forme de chaîne JSON de votre objet est inférieure à 10 Ko. Si vous devez copier des objets plus volumineux, envisagez d'utiliser
ArrayBuffer
ou WebAssembly. Vous pouvez en savoir plus sur ce problème sur
esta publicación de Blog sur postMessage
performance.
Une note sur les outils
Les travailleurs Web ne sont pas encore courants, c'est pourquoi la plupart des outils de module, tels que WebPack
y Retrousser"Ne les soutenez pas depuis le début." (Pack ou pack Bien que oui!) Heureusement, il existe des plugins pour faire des web workers, eh bien, travail avec WebPack et Rollup:
résumer
Pour nous assurer que nos applications sont aussi fiables et accessibles que possible, en particulier dans un marché de plus en plus mondialisé, nous devons prendre en charge des appareils restreints; C'est la manière dont la majorité des utilisateurs accèdent au Web dans le monde entier. OMT offre un moyen prometteur d'augmenter les performances de ces appareils sans nuire aux utilisateurs d'appareils haut de gamme.
De plus, l'OMT présente des avantages secondaires:
Les travailleurs Web n'ont pas à être effrayants. Des outils tels que Comlink suppriment le travail des travailleurs et en font une option viable pour un large éventail d'applications Web.