Le nostre competenze a vostra disposizione sul blog
Amiamo il nostro lavoro e vogliamo condividerlo con voi! Tenetevi aggiornati su tutte le news e le tecnologie con le quali lavoriamo e di cui potreste avere bisogno! Seguite il nostro blog.
×

Error message

The spam filter installed on this site is currently unavailable. Per site policy, we are unable to accept new submissions until that problem is resolved. Please try resubmitting the form in a couple of minutes.
mattia.minotti's picture

JavaScript, di per sè, non è né multi-threaded né puramente single-threaded. La stragrande maggioranza delle implementazioni fornite dai browser Web, tuttavia, è storicamente single-threaded. Solo recentemente, con l’introduzione delle Web Workers API nello standard HTML 5, è stata creata una nozione di multi-threading, che è però strettamente legata all’uso di più file .js e non è ad oggi utilizzata in maniera strutturata come in altri linguaggi di programmazione (C, Java, ecc...).
Visto l’utilizzo legato allo sviluppo di Web UI, il principale limite della natura single-threaded di JavaScript è sempre stato il fatto che eseguire operazioni “costose” dal punto di vista della computazione implicava il freezing temporaneo dell’intera UI. Al crescere della complessità delle applicazioni Web, trasformatesi ormai in RIA (Rich Internet Applications), tale aspetto è cresciuto progressivamente di importanza, ma rimane ancora un problema parzialmente irrisolto.

Single-threading atipico
Sebbene non offra un vero supporto al multi-threading, JavaScript fornisce alcune funzioni come setTimeout e setInterval il cui comportamento può far pensare ad un parallelismo computazionale, ma che in realtà sfruttano solamente meccanismi di priorità di esecuzione per simulare il risultato atteso dall’utente.
In pratica, entrambe le funzioni permettono di inserire una porzione di codice nella coda di esecuzione (singola, in quanto il linguaggio è single-threaded) con una priorità massima che rende probabile, ma non certo, la sua esecuzione in un particolare istante specificato. Ciò che in altri linguaggi si può ottenere tramite l’esecuzione su più thread, si ottiene in JavaScript tramite una sorta di interleaving, governato da meccanismi di priorità.
E’ chiaro che non si tratta di vero multi-threading e che queste funzioni non possono essere utilizzate per sviluppare applicazioni in grado di sfruttare il parallelismo del calcolo in maniera evoluta, ma in alcuni casi esse permettono di aggirare i limiti del linguaggio e ottenere comunque i risultati attesi.

ExtJs 4 e reattività
ExtJs, come ogni framework sviluppato in JavaScript, ne sottoscrive pregi e difetti ed è quindi soggetto ai limiti del modello a single-thread. Un esempio pratico in cui si può toccare con mano queste problematiche è il seguente:

function get_random_color() {
    var letters = '0123456789ABCDEF'.split('');
    var color = '#';
    for (var i = 0; i < 6; i++ ) {
        color += letters[Math.round(Math.random() * 15)];
    }
    return color;
}

Ext.application({
    name: 'TestApp',
    
    launch: function() {
        
        var rightContainer = Ext.create('Ext.container.Container',{
            region:'center'
        });
        
        var menustore = Ext.create('Ext.data.TreeStore', {
            root: {
                expanded: true,
                children: [
                    { text: "choice1", leaf: true },
                    { text: "choice2", leaf: true },
                    { text: "choice3", leaf: true }
                ]
            }
        });

        var menu = Ext.create('Ext.tree.Panel', {
            title: 'Menu',
            region: 'west',
            width: 200,
            store: menustore,
            rootVisible: false,
            listeners: {
                'selectionchange': function(){
                        for(var i=0; i < 20000; i++){
                            console.log('x');
                        }
                        rightContainer.getEl().setStyle('background',get_random_color());
                }
            }
        });
        
        var viewport = Ext.create('Ext.container.Viewport', {
            layout: 'border',
            items: [
                menu,
                rightContainer
            ]
        });
    }
});

Il codice di cui sopra è un semplice snippet che, utilizzando il framework ExtJs 4, renderizza una viewport divisa in due pannelli, sulla sinistra un menù ad albero con tre voci selezionabili e sulla destra un container colorato. Cliccando su una delle voci del menù si simula l’esecuzione di un’operazione computazionalmente pesante (che nell’esempio è la stampa su console di 20.000 messaggi, ma che nel caso reale potrebbe essere la creazione di un’interfaccia complessa) e poi la visualizzazione di un nuovo colore sul container di destra.

'selectionchange': function(){
 	for(var i=0; i < 20000; i++){
             console.log('x');
        }
        rightContainer.getEl().setStyle('background',get_random_color());
}

Eseguendo il codice, si nota subito un risultato inatteso: al click di una voce di menù essa non cambia stato immediatamente, non lo cambia fino a che l’operazione computazionalmente onerosa non è completata. Dal punto di vista dell’utilizzatore, ne risulta una sensazione di non reattività dell’interfaccia che è sicuramente piuttosto sgradevole.
Il motivo di questo comportamento sta nel fatto che, essendo l’esecuzione single-threaded, il framework applica lo stile al componente solo al ritorno della funzione che funge da handler, la quale però richiede parecchio tempo per la sua esecuzione. In un linguaggio che supporta il multi-threading, la soluzione sarebbe stata quella di eseguire tutte le operazioni interne all’handler su un thread secondario e rilasciare immediatamente il controllo del thread principale che si occupa della UI. Come si può ovviare a questo problema in JavaScript?

Il trucco del setTimeout
Esiste un piccolo trucco, che mi sento di proporre, in grado di risolvere il problema della reattività dell’interfaccia grafica e si basa proprio sull’utilizzo della primitiva setTimeout di JavaScript. La soluzione consiste nell’eseguire tutto il blocco dell’handler con un timeout di 1ms, come è mostrato di seguito.

'selectionchange': function(){
 	setTimeout(function(){
               for(var i=0; i < 20000; i++){
                      console.log('x');
               }
               rightContainer.getEl().setStyle('background',get_random_color());
        }, 1);
}

Così facendo, l’esecuzione dell’handler è istantanea e il framework procede al completamento di tutte le sue procedure, compresa la renderizzazione dello stato selezionato della voce di menù. Il corpo della funzione passata come paramentro alla setTimeout viene eseguito con massima priorità dopo 1ms (impercettibile per l’utente) dalla chiamata dell’handler, ma comunque dopo che le varie procedure del framework sono state eseguite. Sostanzialmente in questo modo è stata invertita la priorità di esecuzione del codice, prima l’aggiornamento dell’interfaccia del menù e poi la gestione dell’evento. Il risulato è sicuramente più apprezzabile.

Ovviamente, questo è solo un piccolo trucco che non risolve tutti i problemi della mancanza di un vero multi-threading in JavaScript. Tuttavia esso può essere utile per capire come funziona il ciclo di esecuzione nei moderni browser e come sia possibile costruire applicazioni che, seppur complesse, sono comunque in grado di assicurare un certo livello di reattività dell’interfaccia grafica.

Aggiungi un commento

Filtered HTML

  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.