2017-01-21 11 views
1

У меня вопрос о том, как цикл событий в модуле asyncio python управляет выдающимися задачами. Рассмотрим следующий код:Python asyncio task ordering

import asyncio 

@asyncio.coroutine 
def a(): 
    for i in range(0, 3): 
     print('a.' + str(i)) 
     yield 


@asyncio.coroutine 
def b(): 
    for i in range(0, 3): 
     print('b.' + str(i)) 
     yield 


@asyncio.coroutine 
def c(): 
    for i in range(0, 3): 
     print('c.' + str(i)) 
     yield 


tasks = [ 
    asyncio.Task(a()), 
    asyncio.Task(b()), 
    asyncio.Task(c()), 
] 

loop = asyncio.get_event_loop() 
loop.run_until_complete(asyncio.wait([t1, t2, t3])) 

Запуск этого напечатает:

a.0 
b.0 
c.0 
a.1 
b.1 
c.1 
a.2 
b.2 
c.2 

Обратите внимание, что он всегда печатает «а», то «б», затем «с». Я предполагаю, что независимо от того, сколько итераций проходит каждая сопрограмма, она всегда будет печататься в этом порядке. Таким образом, вы бы никогда не увидеть что-то вроде

b.100 
c.100 
a.100 

Coming из Node.js фона, это говорит мне, что цикл обработки событий здесь поддерживает очередь внутренне, что она использует, чтобы решить, какие задачи для следующего запуска. Сначала он ставит a() в начале очереди, затем b(), затем c(), так как это порядок задач в списке, переданных в asyncio.wait(). Затем всякий раз, когда он попадает в оператор yield, он ставит эту задачу в конце очереди. Я предполагаю, что в более реалистичном примере скажем, если бы вы выполняли асинхронный HTTP-запрос, он вернул бы a() в конце очереди после ответа HTTP-ответа.

Могу ли я получить амен на этом?

ответ

1

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

import asyncio 


@asyncio.coroutine 
def coro(tag, delay): 
    for i in range(1, 8): 
     print(tag, i) 
     yield from asyncio.sleep(delay) 


loop = asyncio.get_event_loop() 

print("---- await 0 seconds :-) --- ") 
tasks = [ 
    asyncio.Task(coro("A", 0)), 
    asyncio.Task(coro("B", 0)), 
    asyncio.Task(coro("C", 0)), 
] 

loop.run_until_complete(asyncio.wait(tasks)) 

print("---- simulate some blocking I/O --- ") 
tasks = [ 
    asyncio.Task(coro("A", 0.1)), 
    asyncio.Task(coro("B", 0.3)), 
    asyncio.Task(coro("C", 0.5)), 
] 

loop.run_until_complete(asyncio.wait(tasks)) 

loop.close() 

Как вы можете видеть, сопрограммы назначаются по мере необходимости, а не в порядке.

+0

Правильно, он будет запускать обратные вызовы, как только это будет возможно, если I/O занимает различное количество времени, чтобы завершить, они начнут выходить из строя. Мое мнение состояло в том, что, если не было никаких операций ввода-вывода (как в моем примере), они всегда запускаются в предсказуемом порядке из-за какого-то управления задачами, происходящими за кулисами (возможно, очереди). В отличие от подобных потоков, которые будут запускать их в соответствии с планировщиком потоков ОС и будут непредсказуемыми. – d512

+0

Если нет ввода-вывода, не используйте asyncio. Они не предсказуемы, и вы не можете быть уверены, как это будет работать в другой реализации или версии или в разных условиях. – Udi

+0

Я ценю ответ, но мой вопрос был не в правильном использовании 'asyncio', а в том, как он реализован. Конечно, небезопасно делать предположения о деталях реализации библиотеки, и я не пытаюсь сказать иначе, задавая этот вопрос. Просто хотелось узнать, знает ли кто, как это работает. – d512