Passer au contenu principal




Une architecture off-the-thread peut considérablement améliorer la fiabilité et l'expérience utilisateur de votre application.

Au cours des 20 dernières années, le Web a considérablement évolué, passant de documents statiques avec certains styles et images à des applications complexes et dynamiques. Cependant, une chose est restée en grande partie inchangée: nous n'avons qu'un seul thread par onglet de navigateur (à quelques exceptions près) pour faire le travail de rendu de nos sites et d'exécuter notre 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, nous pouvons obtenir des fonctionnalités similaires de la part des travailleurs Web, qui existent depuis 2007 et sont pris en charge par les principaux navigateurs depuis 2012. Les travailleurs Web fonctionnent en parallèle avec le thread principal, mais contrairement au thread natif, ils ne peuvent pas partager de 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 worker = new Worker ( "./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 worker = new Worker ( "./worker.js" ) ;
travailleur . postMessage ( [ 40 , 2 ] ) ;

worker.js

addEventListener ( "message" , événement => {
const [ a , b ] = événement . données ;
} ) ;

Pour envoyer un message au fil de discussion principal, utilisez le même postMessage API dans le travailleur Web et configurez un écouteur d'événements dans le thread principal:

main.js

const worker = new Worker ( "./worker.js" ) ;
travailleur . postMessage ( [ 40 , 2 ] ) ;
travailleur . addEventListener ( "message" , événement => {
console . journal ( événement . données ) ;
} ) ;

worker.js

addEventListener ( "message" , événement => {
const [ a , b ] = événement . données ;

postMessage ( a + 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 est une bibliothèque dont le but est de vous permettre d'utiliser des web workers sans avoir à vous soucier des détails de postMessage. Comlink vous permet de partager des variables entre les web workers et le thread principal presque comme des langages de programmation qui prennent en charge nativement le thread.

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 { exposer } depuis "comlink" ;

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

main.js

importer { wrap } depuis "comlink" ;

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

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 être là, comme la mise à jour de l'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

L'équipe Google Chrome a développé PROXX en tant que clone de démineur conforme
Application Web progressive exigences, y compris le travail hors ligne et une expérience utilisateur attrayante. Malheureusement, les premières versions du jeu fonctionnaient mal sur les appareils restreints tels que les téléphones multifonctions, ce qui a conduit l'équipe à se rendre compte que le fil principal était un goulot d'étranglement.

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

Comme le montre l'exemple PROXX, OMT permet à votre application de s'exécuter de manière fiable sur une plus large gamme d'appareils, mais cela ne rend pas votre application plus rapide:

  • 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
cet article 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.

R Marketing Numérique