Skip to the content.

Torna all’indice generazione tempi >Versione in Python

SCHEDULAZIONE CON ASYNC/AWAIT

Base teorica

Gestione con I/O sincrono bloccante in figura a sinistra. Gestione con I/O asincrono e non bloccante nella stessa figura ma a destra.

alt text

Eventi vs thread

alt text

Il modello di gestione della CPU nei SO normalmente è di tipo multithreading preemptive, cioè con interruzione anticipata del task in esecuzione con riassegnazione della risorsa CPU ad un altro task, per almeno due motivi:

Il modello di gestione della CPU in ambienti server come node JS e client come l’ambiente javascript di un browser web, invece, è normalmente a singolo thread dove il multitasking è generato non utilizzando il multithreading ma un modello di esecuzione ad eventi (event driven runtime) composto da:

Callback

Un callback è una funzione che:

Le callback sono il modo principale in cui vengono implementate in un modello ad eventi le azioni di risposta ad un evento, spesso mediante funzioni definite una sola volta nel codice, tipicamente in forma anonima.

Le callback possono essere:

Modello ad eventi

I casi d’uso che potrebbero beneficiare di un modello a thread singolo ad eventi potrebbero essere:

Gli svantaggi sono ascrivibili a:

alt text

La libreria async.io ha un modello di runtime basato su un ciclo di eventi (event loop), che è responsabile:

Questo modello è abbastanza diverso dai modelli in altri linguaggi come C e Java basati su processi e thread. Una proprietà molto interessante è che un linguaggio ad eventi, a differenza di molti altri linguaggi, non blocca mai gli altri task quando si è in attesa di un input sul task corrente.

La gestione dell’I/O viene in genere eseguita tramite eventi e callback:

Il primo messaggio in coda viene di volta in volta estratto e processato per essere eseguito inserendo la sua callback, e tutte le funzioni ad essa annidate, in altrettanti frame sullo stack. La callback correntemente sullo stack, viene eseguita fino a che non ritornano tutte le sottofunzioni ad essa annidate.

Se le operazioni da svolgere nei task sono CPU intensive è buona norma delegarle a fornitori di servizi esterni al thread corrente, questi possono essere servizi in rete oppure servizi in esecuzione su altri thread. Una volta completata l’operazione delegata (può trascorrere un certo tempo), viene richiamata una callback (sul thread del loop degli eventi) con cui si notificano i risultati dell’operazione.

Purtroppo, la libreria standard C++ di Arduino non supporta, per il momento, direttamente il costrutto async/await. Tuttavia, possiamo simulare un comportamento simile utilizzando la libreria di macro async.h , che fornisce un’implementazione della programmazione concorrente su Arduino basata sui Duff’s device su cui si basano i protothread.

Caratteristiche:

Funzioni:

Precauzioni:

Tratto da https://github.com/naasking/async.h

Definizione delle macro async/await

Nelle situazioni in cui non è possibile includere librerie come accade nel simulatore Tinkercad, allora si può inserire in cima al file la definizione delle macro che definiscono i costrutti async/await:

typedef enum ASYNC_EVT { ASYNC_INIT = 0, ASYNC_CONT = ASYNC_INIT, ASYNC_DONE = 1 } async;
#define async_state unsigned _async_k
struct as_state { async_state; };
#define async_begin(k) unsigned *_async_k = &(k)->_async_k; switch(*_async_k) { default:
#define async_end *_async_k=ASYNC_DONE; case ASYNC_DONE: return ASYNC_DONE; }
#define await(cond) await_while(!(cond))
#define await_while(cond) *_async_k = __LINE__; case __LINE__: if (cond) return ASYNC_CONT
#define async_yield *_async_k = __LINE__; return ASYNC_CONT; case __LINE__:
#define async_exit *_async_k = ASYNC_DONE; return ASYNC_DONE
#define async_init(state) (state)->_async_k=ASYNC_INIT
#define async_done(state) (state)->_async_k==ASYNC_DONE
#define async_call(f, state) (async_done(state) || (f)(state))
#define await_delay(delay) \
{ \
  do { \
    static unsigned long as_sleep; \
    as_sleep = millis(); \
    await(millis() - as_sleep > delay); \
  } while(false); \
}

Esempi

Di seguito è riportato un esempio di blink sequenziale in esecuzione su due task separati su scheda ESP32, con IDE Wokwi e con la libreria uasync.io. La programmazione sequenziale del blink del led è emulata tramite una funzione delay() non bloccante await_delay() fornita dalla libreria async.h.

#include "async.h"

bool blink1_running = true;
int led1 = 13;
int led2 = 12;
byte pulsante=2;
as_state pt1, pt2;

async asyncTask1(as_state *pt) {
  async_begin(pt);
  // Loop secondario protothread
  while(true) {
	digitalWrite(led1, HIGH);   // turn the LED on (HIGH is the voltage level)
	await_delay(500);			// delay non bloccanti
	digitalWrite(led1, LOW);    // turn the LED off by making the voltage LOW
	await_delay(500);			// delay non bloccanti
  }
  async_end;
}

async asyncTask2(as_state *pt) {
  async_begin(pt);
  // Loop secondario protothread
  while(true) {
	digitalWrite(led2, HIGH);   // turn the LED on (HIGH is the voltage level)
	await_delay(1000);			// delay non bloccanti
	digitalWrite(led2, LOW);    // turn the LED off by making the voltage LOW
	await_delay(1000);			// delay non bloccanti
  }
  async_end;
}

// the setup function runs once when you press reset or power the board
void setup() {
  // initialize digital pin LED_BUILTIN as an output.
  pinMode(led1, OUTPUT);
  pinMode(led2, OUTPUT);
  /* Initialize the async state variables with async_init(). */
  async_init(&pt1);
  async_init(&pt2);
}

void loop() { // loop principale
  asyncTask1(&pt1);
  asyncTask2(&pt2);
  delay(10);
}

Link simulazione online: https://wokwi.com/projects/393802635182647297

In questo caso, il rilevatore dei fronti è realizzato campionando il valore del livello al loop di CPU attuale e confrontandolo con il valore del livello campionato nello stesso loop ma in un momento diverso stabilito mediante un istruzione waitUntilInputLow(). La funzione, di fatto, esegue un blocco del task corrente in “attesa” della soddisfazione di una certa condizione, senza bloccare l’esecuzione degli altri task. L’attesa è spesa campionando continuamente un ingresso fino a che questo non diventa LOW. Quando ciò accade allora vuol dire che si è rilevato un fronte di discesa per cui, qualora in futuro, in un loop successivo, si determinasse sullo stesso ingresso un valore HIGH, allora si può essere certi di essere in presenza di un fronte di salita.

Pulsante toggle che realizza blink e antirimbalzo realizzato con una schedulazione sequenziale con i ritardi emulati all’interno di task diversi su uno stesso thread. La libreria usata è async.h:

#Alla pressione del pulsante si attiva o disattiva il lampeggo di un led 
#include "async.h"

bool blink1_running = true;
int led1 = 13;
int led2 = 12;
byte pulsante=2;
bool stato;
as_state pt1, pt2, pt3;

async asyncTask3(as_state *pt) {
  async_begin(pt);
  // Loop secondario protothread
  while(true) {
	if(digitalRead(pulsante)==HIGH){		// se è alto c'è stato un fronte di salita
		stato = !(stato); 			// impostazione dello stato del toggle
		await_delay(50);
		await(digitalRead(pulsante)==LOW);  // attendi fino al prossimo fronte di discesa
	}else{
		async_yield;
	}
  }
  async_end;
}

async asyncTask1(as_state *pt) {
  async_begin(pt);
  // Loop secondario protothread
  while(true) {
	if(stato){
		digitalWrite(led1, HIGH);   // turn the LED on (HIGH is the voltage level)
		await_delay(500);						// delay non bloccanti
		digitalWrite(led1, LOW);    // turn the LED off by making the voltage LOW
		await_delay(500);						// delay non bloccanti
	}else{
		digitalWrite(led1, LOW);
		async_yield;
	}	
  }
  async_end;
}

async asyncTask2(as_state *pt) {
  async_begin(pt);
  // Loop secondario protothread
  while(true) {
	digitalWrite(led2, HIGH);   // turn the LED on (HIGH is the voltage level)
	await_delay(1000);			// delay non bloccanti
	digitalWrite(led2, LOW);    // turn the LED off by making the voltage LOW
	await_delay(1000);			// delay non bloccanti
  }
  async_end;
}

// the setup function runs once when you press reset or power the board
void setup() {
  // initialize digital pin LED_BUILTIN as an output.
  pinMode(led1, OUTPUT);
  pinMode(led2, OUTPUT);
  /* Initialize the async state variables with async_init(). */
  async_init(&pt1);
  async_init(&pt2);
  async_init(&pt3);
  stato = false;
}

void loop() { // loop principale
  asyncTask1(&pt1);
  asyncTask2(&pt2);
  asyncTask3(&pt3);
  delay(10);
}

Link simulazione online: https://wokwi.com/projects/393860825891092481

Osservazioni:

Quando si tratta di sistemi embedded, il modello cooperativo presenta due vantaggi.

Sitografia:

Torna all’indice generazione tempi >Versione in Python