2017-02-19 4 views
1

я следующее приложение, которое запускает планировщик периодически обновлять состояние глобальной переменной (ДИКТ):Обновление глобального Dict из нескольких потоков

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) 

Функция импортирован в 5-секундным интервалом задания в другом файле в том же каталоге:

from app import inventory 

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

Это, однако, не работает, как я надеялся. Импортированная функция обновляет инвентарь, но изменение никогда не распространяется на исходный словарь, поэтому либо bump_inventory работает с копией inventory, либо никогда не обновляет ее за пределами области действия. В двух разных терминалах:

]$ 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} 
... 

Каков правильный способ сделать это?

ответ

1

Выяснил это. Все еще не уверен, почему общая переменная не обновляется (моя догадка по-прежнему заключается в том, что это копия), но передача ее в функцию как аргумент работает просто отлично (поскольку мы передаем ссылки на объект, а не на фактический объект). Изменение 5-секундный интервал это работает:

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

Это также удаляет циклический импорт (т.е. удаляет from app import inventory) в другом файле.

3

1- Не нужно использовать apscheduler с асинчио. У вас есть все возможности, которые вам нужно встроить в асинчио, и он хорошо работает с Sanic.

2 Использование глобального состояния не рекомендуется, особенно в сценарии веб-приложения. Вы должны использовать базу данных или Redis. Но если вам нужно состояние приложения по какой-то причине, вы можете сохранить его прямо на объекте app.

В следующем выпуске Sanic будет установлен метод add_task для добавления задач asyncio в приложение. Вы можете установить главный ветвь из Github, если вы хотите использовать это сейчас:

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

Спасибо! Я все еще довольно новичок в асинчио, поэтому это очень полезная информация. Причина, по которой я хочу иметь глобальное состояние, - это не беспокоиться о базе данных и хранить все в памяти. БД SQLite в памяти также является опцией, но данные в инвентаре очень просты (идеально подходят для простого потокобезопасного dict), поэтому кажется, что это избыток. В чем преимущество сохранения глобального состояния в 'app', а не как отдельной переменной? – mart1n

+1

Кроме того, любая версия, когда релиз, который включает 'add_task', будет доступен в PyPI? – mart1n

Смежные вопросы