2017-02-19 2 views
1

je la demande suivante qui exécute un programmateur pour mettre à jour périodiquement l'état d'une variable globale (dict):Mise à jour un dictionnaire global à partir de plusieurs threads

from sanic import Sanic 
from sanic.response import text 
from apscheduler.schedulers.background import BackgroundScheduler 
import bumper 

app = Sanic() 
scheduler = BackgroundScheduler() 

inventory = {1: 1, 2: 2} 

@scheduler.scheduled_job('interval', seconds=5) 
def bump(): 
    bumper.bump() 


@scheduler.scheduled_job('interval', seconds=10) 
def manual_bump(): 
    global inventory 
    inventory[2] += 1 


@app.route("/") 
async def test(request): 
    return text(inventory) 

if __name__ == "__main__": 

    scheduler.start() 
    app.run(host="0.0.0.0", port=8000) 

La fonction importée dans le 5 deuxième emploi intervalle est dans un fichier différent dans le même répertoire:

from app import inventory 

def bump_inventory(): 
    inventory[1] += 1 
    print('new', inventory) 

Cela ne fonctionne cependant pas comme je l'avais espéré. La fonction importée met à jour l'inventaire mais la modification n'est jamais propagée dans le dictionnaire d'origine. Par conséquent, bump_inventory fonctionne sur une copie de inventory ou ne la met jamais à jour en dehors de la portée de la fonction. Dans deux terminaux différents:

]$ python app.py 
2017-02-19 14:11:45,643: INFO: Goin' Fast @ http://0.0.0.0:8000 
2017-02-19 14:11:45,644: INFO: Starting worker [26053] 
new {1: 2, 2: 2} 
new {1: 3, 2: 2} 

]$ while true; do curl http://0.0.0.0:8000/; echo; sleep 1; done 
{1: 1, 2: 2} 
... 
{1: 1, 2: 3} 
... 

Quelle est la bonne façon de procéder?

Répondre

1

Compris. Je ne sais toujours pas pourquoi la variable partagée n'est pas mise à jour (je suppose que c'est une copie) mais la passer dans la fonction en tant qu'argument fonctionne bien (puisque nous passons des références à un objet, pas à l'objet réel). Modification de l'intervalle de 5 secondes à cela fonctionne:

@scheduler.scheduled_job('interval', seconds=5) 
def bump(): 
    global inventory 
    bumper.bump(inventory) 

Cela supprime également l'importation cyclique (à savoir from app import inventory supprime) dans l'autre fichier.

3

1- Il n'est pas nécessaire d'utiliser apscheduler avec asyncio. Vous avez toutes les facilités dont vous avez besoin dans l'asyncio et ça marche bien avec Sanic.

2- L'utilisation de l'état global n'est pas recommandée, en particulier dans un scénario d'application Web. Vous devriez utiliser une base de données, ou Redis. Mais si vous avez besoin d'un état d'application pour une raison quelconque, vous pouvez le stocker directement sur l'objet app.

La prochaine version de Sanic aura une méthode add_task pour vous permettre d'ajouter des tâches asyncio à votre application. Vous pouvez installer la branche principale de Github si vous voulez l'utiliser maintenant:

import asyncio 
from sanic import Sanic 
from sanic.response import text 

app = Sanic() 
app.inventory = {1:1, 2:2} 


async def five_second_job(app): 
    while True: 
     app.inventory[1] += 1 
     await asyncio.sleep(5) 


async def ten_second_job(app): 
    while True: 
     app.inventory[2] += 2 
     await asyncio.sleep(10) 


@app.route("/") 
async def test(request): 
    return text(app.inventory) 

if __name__ == "__main__": 
    app.add_task(five_second_job(app)) 
    app.add_task(ten_second_job(app)) 
    app.run(host="0.0.0.0", port=9000) 
+1

Merci! Je suis encore relativement nouveau dans le domaine de l'asyncose, donc c'est une information très utile. La raison pour laquelle je veux avoir l'état global est de ne pas déranger avec une base de données et de garder tout en mémoire. Une base de données SQLite en mémoire est également une option, mais les données de l'inventaire sont très simples (parfaites pour un dict simple thread-safe), donc cela semble exagéré. Quel est l'avantage de maintenir l'état global dans 'app' plutôt que comme une variable séparée? – mart1n

+1

En outre, toute supposition quand une version qui comprend 'add_task' sera disponible dans PyPI? – mart1n