# Les barres de progression dans Celery
- **Intro**
- Communication Worker->Django
- Communication Django->Client
- Accès concurrent
## Intro
### Liste des courses
- Progression en temps réel
- Barre de progression propre à chaque tâche instanciée
### Contraintes
- Tâches possiblement longues
- Éviter le polling (fonctionner sur évènements)
- Pas de résidus en cas de plantage
### Situation initiale
- Fonctionnalité non existante dans Celery
- Solutions sur le net : progression par comptage de micro-tâches
- Une bibliothèque existante sur le net : celery-progress
### Complexité supplémentaire
---
## Communication worker -> server
- Coté Worker
- Coté Django
- Accès concurrent
---
### Coté Worker
https://docs.celeryq.dev/en/main/reference/celery.events.html
> The worker has the ability to send a message whenever some event happens. These events are then captured by tools like Flower, and celery events to monitor the cluster.
### Coté Worker
- celery.events.EventDispatcher 📢
- celery.events.EventReceiver 👂
### Coté Worker
```python[]
class PtfTask(AbortableTask):
_progress_dispatcher = None
@property
def progress_dispatcher(self):
# Singleton
...
def update_state(self, task_id, state, meta, **kwargs):
self.progress_dispatcher.send(
"ptf-task-progress",
data=meta,
uuid=task_id or self.request.id,
)
return super().update_state(task_id, state, meta, **kwargs)
```
> On envoie un évènement `ptf-task-progress` quand on met à jour l'état de la tâche
### Coté Worker
```python[13]
class PtfTask(AbortableTask):
_progress_dispatcher = None
@property
def progress_dispatcher(self):
# Singleton
...
def update_state(self, task_id, state, meta, **kwargs):
self.progress_dispatcher.send(
"ptf-task-progress",
data=meta,
uuid=task_id or self.request.id,
)
return super().update_state(task_id, state, meta, **kwargs)
```
> Point important: on peut faire avancer la progression de n'importe quelle tâche tant qu'on a son ID!
### Coté Worker
```python
@shared_task(name="crawler.tasks.toto", base=PtfTask, bind=True)
def print_toto(self, toto = "toto"):
meta = {"current": 0, "total": len(toto)}
# Initialize progress bar at 0
self.update_state(
meta=meta,
state=states.STARTED,
)
for index, letter in enumerate(toto):
# Do work
print(letter)
# Update progress bar
meta["current"] = index
self.update_state(
meta=meta,
state=states.STARTED,
)
```
---
### Coté Django
```python
from celery import Celery, current_app
def send_sse_message(event):
...
handlers = {
"ptf-task-progress": send_sse_message,
}
receiver = current_app.events.Receiver(
channel=current_app.connection_for_read(), app=current_app, handlers=handlers
)
```
> On envoie un message au client quand on reçoit un évènement `ptf-task-progress`
### Coté Django
Django 4.2 Workaround