Dans la programmation séquentielle, je rencontre constamment le désir évident de ne pas arrêter le programme à un moment où l'objectif de certaines tâches (processus) est des actions périodiques - par exemple, interroger les valeurs des capteurs, ou transmettre des données sur un calendrier à un serveur, ou entrée / sortie d'une grande quantité de données. La chose la plus simple, bien sûr, est d'attendre la fin de l'événement périodique et ensuite, lentement, de continuer à effectuer d'autres tâches.
while True: do_ext_proc_before() do_internal_proc() sleep(5) do_ext_proc_after()
Vous pouvez abandonner 'sleep ()' et activer la vérification de certaines conditions dans la boucle, ce qui vous permettra de ne pas retarder la boucle principale au moins jusqu'à ce qu'un événement périodique se produise:
start = time() set_timer(start,wait=5)
En programmation asynchrone, chaque tâche devient un processus indépendant et est exécutée, selon l'implémentation spécifique, en parallèle ou pseudo-parallèle, en utilisant une compréhension interne des conditions d'attente naturelles ou artificiellement établies ou l'utilisation d'une ressource limitée, par exemple, un disque ou un canal de communication.
setTask(do_ext_proc_before()) setTask(do_internal_proc(),timer=5,timeout=7,alarm_handler=alarm) setTask(do_ext_proc_after()) runTasks()
Maintenant, un problème se pose qui n'existe pas dans la programmation séquentielle - que faire s'il est nécessaire de synchroniser certains processus avec leur asynchrone
faire? Par exemple, après avoir reçu des données de capteurs, lancez le processus d'envoi de données à un serveur ou répondez à une urgence. De plus, dans la programmation asynchrone, l'organisation des entrées / sorties asynchrones est résolue de manière organique dans la norme de langage, et d'autres situations sont résolues dans les bibliothèques.
J'ai étudié cette question en utilisant la bibliothèque étendue asyncio Micropython publiée
Peter Hinch ( https://github.com/peterhinch/micropython-async/blob/master/TUTORIAL.md )
La solution la plus simple consiste à signaler l'événement aux processus intéressés. Pour ce faire, utilisez la classe Event (), qui contient plusieurs modules
Event.Set( timeout = None, data = None ) - (Event = True), , , Event.IsSet() - , , True, False Event.Wait() - , - Done,Timeout,Cancel Event.Data() - , Event.Clear() - (Event = False).
L'achèvement est enregistré, en règle générale, par le processus qui attend que l'événement se produise, par exemple, le processus d'affichage à l'écran ou le processus d'enregistrement des données sur le disque, ou le délai d'expiration, puis il n'est pas nécessaire de mettre à jour ou d'enregistrer les données, car elles ne sont pas mises à jour pour une raison quelconque, ou en raison de son interruption lors de la survenance d'un autre événement important, par exemple la transition vers le mode veille ou le redémarrage, ce qui peut nécessiter la libération de tous les processus en attente en réinitialisant les événements correspondants.
Il convient de garder à l'esprit qu'il est conseillé de faire Event.Clear () avec un seul processus, si cela ne contredit pas l'algorithme donné. Sinon, si plusieurs processus attendent que l'événement Event.Set () se produise, il est supposé que Event.Clear () doit être exécuté par l'un des processus intéressés, en s'assurant uniquement que tous les processus intéressés ont répondu à l'événement. Cela complique la logique de décision lors de l'utilisation de Event-Class lors de l'attente d'un événement par plusieurs processus. Cette situation est résolue en établissant une certaine quantité de Clear () de l'événement qui s'est produit.
Barrier.Set( quantity = 1, timeout = None, data = None ) - quantity = 1 Event.Set() Barrier.IsSet() - , , True, False Barrier.Wait() - , - Done,Timeout,Cancel Barrier.Data() - , Barrier.qty - Barrier.Clear() - (Event = False), Barrier.quantity , ,
En même temps, aucune comptabilité n'est tenue - quel processus spécifique a déjà répondu, et qui ne l'est pas encore, ce qui peut poser le problème de la réaction de l'événement, si cela est essentiel pour un algorithme donné. Si au lieu de Barrier.quantity vous passez une liste de noms de processus intéressés, ce conflit peut être évité. De plus, en cas d'expiration ou d'interruption de l'événement, vous pouvez déterminer quels processus spécifiques en attente n'ont pas encore fonctionné. Tout ce qui précède s'applique à une situation où un ou plusieurs processus attendent l'occurrence d'un certain événement, ou une situation un-à-plusieurs. Cela se produit lorsque le ou les processus do_ext_proc_after () pendant la programmation séquentielle ne seront exécutés qu'après l'achèvement de do_internal_proc (). Pour faciliter la compréhension, nous allons étendre la classe d'événement et la classe barrière existantes à la nouvelle classe EEvent et la rendre ou les objets générés par elle - global. Ici, «créateurs» est le nom ou la liste des noms des processus qui déclenchent l'événement ou déverrouillent la ressource, «folowers» est le nom ou la liste des noms des processus qui attendent l'événement ou déverrouillent la ressource
EEvent.Set (creators, folowers, timeout = None, data = None ) - True, EEvent.IsSet( procName ) - procName - ID EEvent.Wait( procName ) EEvent.Clear( procName ) EEvent.Folowers() - , . Barrier.qty = len(EEvent.List()) EEvent.Creators() - ,
En utilisant les modules EEvent-Class, nous pouvons décrire la solution au problème discuté précédemment.
def do_internal_proc(): ... EEvent.Set ('p_Creator',('p_Folwer1','p_Folwer2'))
Considérez la situation inverse - lorsqu'un processus attend l'achèvement de plusieurs événements, ou une situation «plusieurs à un». En d'autres termes, si l'exécution de do_internal_proc () ne peut avoir lieu qu'après l'exécution de do_ext_proc_before (). Dans le cas extrême, lorsqu'un processus attend l'achèvement / l'occurrence d'un événement, la tâche peut être résolue à l'aide de la classe Event. Lorsque l'achèvement de plusieurs événements est attendu, par exemple, uniquement après avoir affiché les données reçues et les avoir envoyées au serveur, les avoir enregistrées sur le disque, il est nécessaire que chaque processus exécuté établisse sa participation à l'événement attendu et attende que tous les processus soient terminés.
def do_ext_proc_before1() ... EEvent.Set('p_Creator1','p_Folwer') ... def do_ext_proc_before2() ... EEvent.Set('p_Creator2','p_Folwer') ... def do_internal_proc(): ... EEvent.Wait(('p_Creator1','p_Creator2')) ... EEvent.Clear('p_Folwer')
Un autre aspect important de la programmation asynchrone est le partage d'une ressource limitée. Par exemple, la mise à jour des données doit être effectuée par un seul processus, le reste des processus réclamant une action similaire doit faire la queue ou attendre que les données soient mises à jour. Dans le même temps, il est possible que la lecture des données pour l'affichage ou le transfert ne soit pas critique. Par conséquent, il est nécessaire de connaître la liste des processus concurrents lors de l'organisation d'événements pertinents.
Dans la norme de programmation asynchrone, cette tâche est résolue par les modules Lock-Class. Dans le cas général, le problème peut également être résolu de manière similaire à la situation un-à-plusieurs.
def do_internal_proc():
En plus des options envisagées, il existe des solutions qui limitent le débit, organisent les files d'attente et la planification contrôlée des processus, mais dans mon activité, il n'y a pas encore eu besoin de cela et, par conséquent, un besoin de compréhension suffisante pour moi-même, bien que je n'exclue pas qu'il y ait plus élégant ou décisions économiques.
En conclusion, je veux dire que les approches séquentielles et asynchrones ont le même droit d'exister et de mettre en œuvre avec succès les algorithmes donnés. Par conséquent, l'application de telle ou telle approche est déterminée par les priorités du créateur - ce qui est plus important pour lui lors de la mise en œuvre des algorithmes donnés - la transparence et la lisibilité, la vitesse ou le volume du code résultant.