Ajout de Web Workers au site statique créé avec React et Python

Dans mes précédents articles j'ai créé un site statique avec react et python , puis j'ai abordé les problèmes d'évolutivité . Dans cet article, je présente les Web Workers afin d'exécuter le code Python dans son propre thread, libérant ainsi le thread de l'interface utilisateur. Vous pouvez trouver le code source ici , et le site statique sur les pages GitHub ici .

Travailleurs Web

Ma solution précédente fonctionnait bien pour la simple tâche de générer des matrices, mais si je faisais quelque chose de coûteux en calcul, cela bloquerait le thread de l'interface utilisateur et l'application ne répondrait plus jusqu'à ce que le calcul revienne. J'ai besoin d'exécuter le code python dans un autre thread. Dans le navigateur, cela se fait avec un « travailleur Web ».

La documentation de pyodide pour cela est très claire, le problème principal est de savoir comment le brancher sur React. Le problème est que le processus de génération de React utilise webpack pour regrouper tous les fichiers source individuels dans quelques fichiers volumineux, mais l'objet de travail prend un fichier JavaScript nommé dans son constructeur . Il existe quelques packages open source pour gérer cela. J'ai choisi worker-loader car il s'agit d'un package de contribution à "webpack", et fera partie de la prochaine distribution de base.

Nous pouvons l'ajouter au projet précédent de la manière habituelle :

npm install --save worker-loader

import PythonWorker from 'worker-loader!./pythonWorker.js' // eslint-disable-line import/no-webpack-loader-syntax

Maintenant, j'avais besoin d'un moyen d'appeler le code dans le worker. La communication avec le travailleur passe par une publication et un abonnement à des « messages » qui sont de simples objets JavaScript. Plutôt que d'écrire des messages spéciaux pour chaque fonction, j'ai choisi d'utiliser le package comlink qui crée un wrapper autour de la connexion permettant des appels de fonction simples.

npm install --save comlink

import * as Comlink from 'comlink/dist/esm/comlink'
self.importScripts('https://cdn.jsdelivr.net/pyodide/v0.17.0/full/pyodide.js')
const PYTHON_CODE = `
import random
import numpy as np
def generate_matmul_exercise(max_rows, max_cols):
    m = random.randint(1, max_rows)
    n = random.randint(1, max_cols)
    p = random.randint(1, max_cols)
rng = np.random.default_rng()
    A = rng.integers(low=-10, high=10, size=(m, n), dtype=np.int32)
    B = rng.integers(low=-10, high=10, size=(n, p), dtype=np.int32)
    C = A @ B
return {
        'm': m,
        'n': n,
        'p': p,
        'A': A,
        'B': B,
        'C': C
    }
def generate_matadd_exercise(max_rows, max_cols):
    m = random.randint(1, max_rows)
    n = random.randint(1, max_cols)
rng = np.random.default_rng()
    A = rng.integers(low=-10, high=10, size=(m, n), dtype=np.int32)
    B = rng.integers(low=-10, high=10, size=(m, n), dtype=np.int32)
    C = A + B
return {
        'm': m,
        'n': n,
        'A': A,
        'B': B,
        'C': C
    }    
`
function wrapGenerateMatmulExercise() {
  const func = self.pyodide.globals.get('generate_matmul_exercise') // eslint-disable-line no-restricted-globals
return (maxNumberOfRows, maxNumberOfColumns) => {
    console.log('generateMatmulExercise')
    const obj = func(maxNumberOfRows, maxNumberOfColumns)
    const results = {
      m: obj.get('m'),
      n: obj.get('n'),
      p: obj.get('p'),
      A: obj
        .get('A')
        .toJs()
        .map((x) => Array.from(x)),
      B: obj
        .get('B')
        .toJs()
        .map((x) => Array.from(x)),
      C: obj
        .get('C')
        .toJs()
        .map((x) => Array.from(x))
    }
    obj.destroy()
    return results
  }
}
function wrapGenerateMataddExercise() {
  const func = self.pyodide.globals.get('generate_matadd_exercise') // eslint-disable-line no-restricted-globals
return (maxNumberOfRows, maxNumberOfColumns) => {
    const obj = func(maxNumberOfRows, maxNumberOfColumns)
    const results = {
      m: obj.get('m'),
      n: obj.get('n'),
      A: obj
        .get('A')
        .toJs()
        .map((x) => Array.from(x)),
      B: obj
        .get('B')
        .toJs()
        .map((x) => Array.from(x)),
      C: obj
        .get('C')
        .toJs()
        .map((x) => Array.from(x))
    }
    obj.destroy()
    return results
  }
}
async function loadPyodideAndPackages() {
  await self.loadPyodide({
    indexURL: 'https://cdn.jsdelivr.net/pyodide/v0.17.0/full/'
  }) // eslint-disable-line no-restricted-globals
  await self.pyodide.loadPackage(['numpy']) // eslint-disable-line no-restricted-globals
  self.pyodide.runPython(PYTHON_CODE) // eslint-disable-line no-restricted-globals
}
const pyodideReadyPromise = loadPyodideAndPackages()
const pythonContext = {
  async setup() {
    await pyodideReadyPromise
    this.generateMatmulExercise = wrapGenerateMatmulExercise()
    this.generateMataddExercise = wrapGenerateMataddExercise()
  }
}
Comlink.expose(pythonContext)

Étant donné que le code sera exécuté dans un thread séparé, les fonctions Python n'ont plus besoin d'être "async", et les waits et les asyncs sont supprimés du code wrapper.

Auparavant, j'avais utilisé une balise de script dans "public/index.html" pour importer le code pyodide, mais cela ne sera pas disponible pour le thread de travail. Pour gérer cela, je supprime la balise de script et ajoute le self.importScriptsau début du code du travailleur. Notez l'utilisation de selfpour accéder au contexte global du travailleur.

Enfin, le bloc de code à la fin après que les wrappers ont chargé le pyodide WebAssembly, le package numpy et le code Python local. Enfin, je crée l' pythonContextobjet, qui est encapsulé par comlink. Initialement, cet objet a une seule fonction asynchrone. setup.Lorsqu'il est appelé, il attend la fin de l'initialisation de pyodide, ajoute les fonctions à l'objet enveloppé.

Création du travailleur

La dernière étape consiste à créer le worker dans « src/App.js ». Tout cela nécessitait un changement mineur de la componentDidMountfonction.

async componentDidMount() {
    const pythonWorker = Comlink.wrap(new PythonWorker())
    await pythonWorker.setup()
    this.setState({ pythonWorker })
  }

npm start

Suggested posts

Éviter les conséquences dévastatrices d'un accident vasculaire cérébral : apprendre du parcours d'une famille.

Éviter les conséquences dévastatrices d'un accident vasculaire cérébral : apprendre du parcours d'une famille.

Introduction Un accident vasculaire cérébral est une urgence médicale qui peut frapper n'importe qui à tout moment. Elle survient lorsque le flux sanguin vers une partie du cerveau est perturbé, entraînant une perte soudaine de la fonction cérébrale.

Comment exécuter gratuitement les plugins ChatGPT avec Langchain

Comment exécuter gratuitement les plugins ChatGPT avec Langchain

Vous devez avoir entendu parler de tous les nouveaux plugins ChatGPT puissants comme le navigateur, l'interpréteur de code, etc. Mais il y a un problème, l'accès aux plugins est limité à une liste d'attente et donc beaucoup n'y ont pas accès.

Related posts

Rassembler OSINT pour la chasse aux menaces

Rassembler OSINT pour la chasse aux menaces

Salut les cyber-amateurs ! Bienvenue à nouveau dans la série OSINT pour la chasse aux menaces. Dans le premier article, je vous ai donné un aperçu d'OSINT et de son importance dans la chasse aux menaces.

Financement de l'élimination du carbone au cours de l'exercice 24

La répartition des demandes de crédits de Carbon180 selon les filières

Financement de l'élimination du carbone au cours de l'exercice 24

Par Alyson Lee, conseillère en gestion des affaires gouvernementales Le processus annuel d'affectation des crédits du gouvernement fédéral est au cœur de nos efforts pour financer l'élimination du dioxyde de carbone (CDR). Chaque année, Carbon180 soumet des demandes à divers bureaux du Congrès pour aider à orienter les flux de dollars de l'année prochaine.

Plus de mots de passe - comment les clés d'accès remplaceront votre mot de passe

Plus de mots de passe - comment les clés d'accès remplaceront votre mot de passe

Pour éviter toute ambiguïté, les termes suivants seront utilisés ; Authentificateur ; Le téléphone mobile utilisé pour effectuer la connexion au site Web ; Le site Web ou l'application que l'utilisateur utilise pour effectuer la demande de connexion au service ; Le fournisseur fournissant le service donné, par exemple

Le meurtre de Jordan Neely et le problème du racisme « patriotique »

Les États-Unis peuvent-ils prendre la mesure essentielle de ne pas assimiler un comportement raciste au fait d'être un bon Américain ?

Le meurtre de Jordan Neely et le problème du racisme « patriotique »

[Alerte de déclenchement : cet essai comprend des captures d'écran de personnes faisant l'éloge du meurtrier de Jordan Neely.] Les responsables de l'application des lois ont finalement - et peut-être à contrecœur - inculpé Daniel Penny, l'homme qui a étouffé Jordan Neely jusqu'à sa mort, pour cet homicide.