Асинхронное программирование с использованием asyncio. Асинхронное программирование с использованием asyncio. Асинхронное программирование с использованием asyncio.

Асинхронное программирование с использованием asyncio.

Open in new window

Асинхронное программирование с использованием asyncio.

Допустим у нас есть следующий словарь.

store = {
    'place1' : [],
    'place2' : []
}

Заполним список по ключу place1 100 словарями.

store = {
    'place1' : [],
    'place2' : []
}

for i in range(100):
    store['place1'].append({'name':  'brick %s' %i})

print(store)

Вывод

{‘place2’: [], ‘place1’: [{‘name’: ‘brick 0’}, {‘name’: ‘brick 1’}, {‘name’: ‘brick 2’},…

Теперь стоит задача перенести все объекты кирпичей из place1 в place2.

Можно определить функцию для этого и 100 раз ее вызвать.

store = {
    'place1' : [],
    'place2' : []
}

for i in range(100):
    store['place1'].append({'name':  'brick %s' %i})

def move(store):
    brick = store['place1'].pop(0)
    store['place2'].append(brick)

for i in range(100):
    move(store)

print(store)

При этом мы в функции выталкиваем первый кирпич из одного списка.

brick = store['place1'].pop(0)

и далее заталкиваем во второй.

store['place2'].append(brick)

Предположим, что этот процесс у нас занимает много времени, добавим в него задержку в 1 сек.

import time
def move():
    time.sleep(1)
    brick = store['place1'].pop(0)
    print('Moving %s' % brick['name'])
    store['place2'].append(brick)


shell>python3 ex.py 
Moving brick 0
Moving brick 1
Moving brick 2

Получается, что весь процесс у нас займет 100 сек.

Как можно его ускорить?

Для этого нужно запустить эту функцию в несколько потоков, или асинхронно.

Для реализации асинхронного запуска необходимо иметь цикл событий.

В этом цикле система будет контролировать выполнение одновременно-выполняющихся задач, предоставляя механизм переключения между ними.

Таким образом после выполнения своей работы, функция будет передавать управления циклу событий, который в свою очередь, будет запускать другие асинхронные задачи.

Библиотека Asyncio позволяем разбивать ваш код на процедуры (функции), которые определять как корутины, что даёт возможность управлять ими как пожелаете, включая и одновременное выполнение.

За переключение контекста в asyncio отвечает await, который передаёт управление обратно в event loop (цикл событий), а тот в свою очередь — к другой корутине.

Выражение после await должно представлять специальный awaitable объект.

Существует 3 варианта таких объектов.

  1. Другая сопрограмма.

  2. Сопрограмма на основе генератора.

  3. Специальный объект, у которого реализован магический метод await, возвращающий итератор.

Мы можем воспользоваться функцией asyncio.sleep(1), которая возвратит awaitable объект, эмулируя задержку.

Добавим функцию цикл от 0 до 50 и обьявим ее как корутину с помощью async.

async def move(store):
    for i in range(50):
        brick = store['place1'].pop(0)
        print('Moving %s' % brick['name'])
        store['place2'].append(brick)
        await asyncio.sleep(1)

Теперь необходимо создать список из 2-х таких функций.

tasks = [
    ioloop.create_task(move(store)),
    ioloop.create_task(move(store))
]

Корутины могут быть запущены только из другой корутины, или обёрнуты в задачу с помощью create_task. Мы воспользовались ioloop.create_task() для того, обёрнуть корутину в задачу.

После того, как у нас оказались 2 задачи, объединим их, используя wait

asyncio.wait(tasks)

И, наконец, отправим на выполнение в цикл событий через run_until_complete

ioloop.run_until_complete(asyncio.wait(tasks))

Полный код примера.

store = {
    'place1' : [],
    'place2' : []
}
import asyncio

for i in range(100):
    store['place1'].append({'name':  'brick %s' %i})

async def move(store):
    for i in range(50):
        brick = store['place1'].pop(0)
        print('Moving %s' % brick['name'])
        store['place2'].append(brick)
        await asyncio.sleep(1)


ioloop = asyncio.get_event_loop()

tasks = [
    ioloop.create_task(move(store)),
    ioloop.create_task(move(store))
]

ioloop.run_until_complete(asyncio.wait(tasks))
ioloop.close()

print(store)

Мы можем вызывать одни корутины из других, связывая их в цепочки.

Например, если мы хотим логировать процесс в файл в другой корутине.

async def logger(brick):
    with open('log.txt', 'a') as f:
        f.write('Log: moving %s' % brick['name'])
    await asyncio.sleep(1)


async def move(store):
    for i in range(50):
        brick = store['place1'].pop(0)
        print('Moving %s' % brick['name'])
        store['place2'].append(brick)
        await logger(brick)

Или создать класс с методом await, который работает как генератор с функцией yield.

class Loger(object):
    def __init__(self, brick):
        self.brick = brick
    def __await__(self):
        with open('log.txt', 'a') as f:
            f.write('Log: moving %s' % self.brick['name'])
        yield


for i in range(100):
    store['place1'].append({'name':  'brick %s' %i})

import time
async def move(store):
    for i in range(50):
        brick = store['place1'].pop(0)
        print('Moving %s' % brick['name'])
        store['place2'].append(brick)
        await Loger(brick)

Other topics