Я узнаю об asyncio Python из книги О'Рейли «Использование Asyncio в Python».
В этой книге есть пример асинхронного цикла for:
# Example 3-26. Easier with an async generator
import asyncio
# Mock Redis interface
class Redis:
async def get(self, key):
await asyncio.sleep(1)
return 'value'
# Mock create_redis
# Real one: aioredis.create_redis
async def create_redis(socket):
await asyncio.sleep(1)
return Redis()
async def do_something_with(value):
await asyncio.sleep(1)
# Our function is now declared with async def , making it a coroutine
# function, and since this function also contains the yield keyword, we refer
# to it as an asynchronous generator function.
async def one_at_a_time(redis, keys):
for k in keys:
# We don’t have to do the convoluted things necessary in the previous
# example with self.ikeys: here, we just loop over the keys directly
# and obtain the value...
value = await redis.get(k)
# ...and then yield it to the caller, just like a normal generator.
yield value
# The main() function is identical to the version in Example 3-25.
async def main():
redis = await create_redis(('localhost', 6379))
keys = ['Americas', 'Africa', 'Europe', 'Asia']
async for value in one_at_a_time(redis, keys):
await do_something_with(value)
start = time.time()
asyncio.run(main())
end = time.time()
print(end - start)
#print result is 9.012349128723145
Но я думаю, что он не запускается асинхронно. Это мой код синхронизации для сравнения с приведенным выше кодом:
class Redis:
def get(self, key):
time.sleep(1)
return 'value'
def create_redis(socket):
time.sleep(1)
return Redis()
def do_something_with(value):
time.sleep(1)
def one_at_a_time(redis, keys):
for k in keys:
value = redis.get(k)
yield value
# The main() function is identical to the version in Example 3-25.
def main():
redis = create_redis(('localhost', 6379))
keys = ['Americas', 'Africa', 'Europe', 'Asia']
tasks = []
for value in one_at_a_time(redis, keys):
do_something_with(value)
start = time.time()
main()
end = time.time()
print(end-start)
# print result 9.025717973709106
Время выполнения кода такое же.
Является ли первый код асинхронным кодом? Я думаю, что это не так, и я не могу понять, почему в книге написано, что это асинхронный код.
ваш код выполняет только одну задачу redis.get()
, и он не может работать быстрее, потому что ему приходится ждать завершения этой задачи, чтобы запустить следующую redis.get()
. Возможно, если вы запустите его create_task()
с помощью redis.get()
, то он запустит множество задач одновременно. См. примеры в документации: Сопрограммы и задачи
Ваш код выполняет одну задачу redis.get(k)
и ожидает ее завершения, чтобы выполнить следующую redis.get(k)
, поэтому он не может работать быстрее.
Вам придется создать более сложный код, чтобы запускать множество задач одновременно.
Этот код создает все задачи (без использования await) и использует gather()
для их одновременного запуска.
async def one_at_a_time(redis, keys):
tasks = []
for k in keys:
tasks.append(redis.get(k))
results = await asyncio.gather(*tasks)
for item in results:
yield item
И это занимает всего 6,0 секунды.
Но он все равно может работать быстрее, потому что на это уходит results
1 секунда вместо 4 секунд, но тратится время на запуск do_something_with()
один за другим, а не на их одновременное выполнение.
Полный рабочий код:
import asyncio
import time
import random
class Redis:
async def get(self, key):
print('get')
await asyncio.sleep(1)
return random.randint(0, 10)
async def create_redis(socket):
await asyncio.sleep(1)
return Redis()
async def do_something_with(value):
print('do something with', value)
await asyncio.sleep(1)
async def one_at_a_time(redis, keys):
tasks = []
for k in keys:
tasks.append(redis.get(k))
results = await asyncio.gather(*tasks)
print('results:', results)
for item in results:
yield item
async def main():
redis = await create_redis(('localhost', 6379))
keys = ['Americas', 'Africa', 'Europe', 'Asia']
async for value in one_at_a_time(redis, keys):
await do_something_with(value)
start = time.time()
asyncio.run(main())
end = time.time()
print('time:', end - start)
#print result is 6.0089991092681885
А вот код, который использует тот же метод, что и gather()
чтобы запустить все do_something_with()
одновременно.
И это дает время 3,0 секунды.
import asyncio
import time
import random
class Redis:
async def get(self, key):
print('get')
await asyncio.sleep(1)
return random.randint(0, 10)
async def create_redis(socket):
await asyncio.sleep(1)
return Redis()
async def do_something_with(value):
print('do something with', value)
await asyncio.sleep(1)
async def one_at_a_time(redis, keys):
tasks = []
for k in keys:
tasks.append(redis.get(k))
results = await asyncio.gather(*tasks)
print('results:', results)
for item in results:
yield item
async def main():
redis = await create_redis(('localhost', 6379))
keys = ['Americas', 'Africa', 'Europe', 'Asia']
tasks = []
async for value in one_at_a_time(redis, keys):
tasks.append(do_something_with(value))
await asyncio.gather(*tasks)
print('start')
start = time.time()
asyncio.run(main())
end = time.time()
print('time:', end - start)
#print result is 3.0061380863189697
#print result is 6.0089991092681885
#print result is 9.012349128723145
Более простой пример, показывающий разницу между
await function()
...
x = function()
...
await x
x = create_task(function())
...
await x
import asyncio
import time
async def my_function(text):
print(text, 'my_function 1')
await asyncio.sleep(0.5)
print(text, 'my_function 2')
await asyncio.sleep(0.5)
print(text, 'my_function 3')
await asyncio.sleep(0.5)
print(text, 'my_function 4')
await asyncio.sleep(0.5)
async def example1():
# create, start, and wait for result (blocking)
await my_function('A')
await my_function('B')
await asyncio.sleep(1)
print('something else 1')
await asyncio.sleep(1)
print('something else 2')
await asyncio.sleep(1)
print('something else 3')
await asyncio.sleep(1)
print('something else 4')
await asyncio.sleep(1)
async def example2():
# create (not blocking)
a = my_function('A')
b = my_function('B')
await asyncio.sleep(1)
print('something else 1')
await asyncio.sleep(1)
print('something else 2')
await asyncio.sleep(1)
print('something else 3')
await asyncio.sleep(1)
print('something else 4')
await asyncio.sleep(1)
# start, and wait for result (blocking)
await a
await b
async def example3():
# create and start (not blocking)
a = asyncio.create_task(my_function('A'))
b = asyncio.create_task(my_function('B'))
await asyncio.sleep(1)
print('something else 1')
await asyncio.sleep(1)
print('something else 2')
await asyncio.sleep(1)
print('something else 3')
await asyncio.sleep(1)
print('something else 4')
await asyncio.sleep(1)
# wait for result (blocking)
await a
await b
# OR
#await asyncio.gather(a, b)
async def main():
print('--- example 1 ---')
start = time.time()
await example1()
end = time.time()
print('time:', (end - start))
print('--- example 2 ---')
start = time.time()
await example2()
end = time.time()
print('time:', (end - start))
print('--- example 3 ---')
start = time.time()
await example3()
end = time.time()
print('time:', (end - start))
asyncio.run(main())
Спасибо за ответ, я ждал этого ответа
Ваш код не обязательно будет работать быстрее только потому, что вы используете asyncio. Проверьте этот пост, чтобы узнать, поможет ли он: stackoverflow.com/questions/1050222/…