La critique que l’on entend le plus souvent au sujet d’ActionScript concerne l’absence de gestion de la concurrence, c’est à dire l’impossibilité d’effectuer simultanément plusieurs actions (ou du moins avec l’apparence de la simultanéité). En effet, le modèle de gestion des évènements en flash est le suivant: toute action, que ce soit celle d’un utilisateur manipulant un composant graphique ou bien le déclenchement d’un timer, sera systématiquement stockée dans une file d’évènements, et tous les évènements de cette file seront exécuté les uns après les autres.

La plupart du temps cette limitation n’est pas trop dérangeante dans la mesure ou les applications flash/flex nécessitant des calculs lourds ne sont pas courantes; Néanmoins, il peut exister quelque cas où la gestion du multithread devient nécessaire. Dans ces situations là, le problème peut sembler insoluble. Il existe pourtant différentes solutions permettant de contourner de manière plus ou moins efficace cette restriction. Toutes reposent sur la même idée: décomposer un traitement long en une multitude de traitements plus cours; mais toutes ne sont pas équivalentes en terme de performance.

La méthode la plus simple est d’utiliser la fonction callLater qui permet de différer l’exécution d’une fonction: l’évènement que constitue l’exécution prochaine de la fonction est alors placé dans la file d’évènement.
Si l’on part du principe que notre algorithme d’origine ressemble à ça:

public function traitementBoucleTresLong(max:int):void {
	for(var i:int = 0; i < max; i++) {
		// Debut du code a exécuter pour i
		// ...
		// Fin du code a exécuter pour i
	}
}

Alors il nous suffira de le modifier de cette manière:

public function traitementPlusCourt(i:int, max:int):void {
	// Debut du code a exécuter pour i
	// ...
	// Fin du code a exécuter pour i
 
	if(i < max)
		FlexGlobals.topLevelApplication.callLater(traitementPlusCourt, [i + 1, max]);
}

Cette méthode fonctionne mais elle pose de gros problèmes en terme de performance. En fait, si on part du principe qu’une seule itération de notre boucle met 1 milliseconde pour aboutir, alors la deuxième version de notre algorithme mettra environ 40 fois plus de temps que la première à s’exécuter. Comment peut on en être aussi sûr? En fait, c’est là que la raison pour laquelle flash a été crée se rappelle à nous: n’oublions jamais qu’à l’origine flash a été conçu pour réaliser des animations. Or une animation est, par définition, constituée d’une succession d’images statiques se succédant de manière rapide pour donner l’illusion du mouvement. Au cinéma la norme est de 24 images par seconde, et pour flash c’est pareil. 1 seconde équivaut à 1.000 millisecondes. Divisez 1.000 par 24 et vous obtenez environ 41 millisecondes, soit le temps minimal et incompressible accordé par flash à l’exécution d’un évènement. Si une itération met 1 milliseconde à s’exécuter, cela signifie donc que le deuxième algorithme attendra une quarantaine de milliseconde supplémentaires avant de passer la main, et ce à chaque itération. Pas très optimisé n’est ce pas?

Pour éviter de gaspiller autant de ressources inutilement, l’astuce consiste à enchaîner les appels de fonction tant que les 40 millisecondes et quelque ne sont pas écoulées (on veillera juste a arrêter un peu avant la fin du laps de temps imparti). En revanche il devient plus délicat de passer des arguments à la fonction de traitement, on préférera donc créer des variables globales.

private var i:int;
 
private var max:int;
 
public function run():void {
	var dateDebut:Number = getTimer();
	while(i < max && getTimer() - dateDebut < 40 - 1)
		traitementPlusCourt2();
	if(i < max)
		FlexGlobals.topLevelApplication.callLater(run);
}
 
public function traitementPlusCourt2():void {
	// Debut du code a exécuter pour i
	// ...
	// Fin du code a exécuter pour i
}

Le fait de pouvoir bricoler ces threads avec trois fois rien explique qu’on leur ait donné le nom de Green Threads. Pour info, les premières versions de java ne géraient pas le multithread autrement que de cette manière. L’auteur de l’article sur lequel je me suis appuyé pour écrire ce post a crée une librairie permettant d’utiliser facilement cette technique sans devoir réinventer la roue à chaque fois. Il y a même inclus un outils de statistique pour en savoir plus sur le temps total d’exécution de votre algorithme, le nombre d’itérations etc.