Как сделать «настоящую параллельную» сопрограмму

Я пытаюсь сделать это:

«Цикл событий запускается в потоке (обычно в основном потоке) и выполняет все обратные вызовы и задачи в своем потоке. Пока задача выполняется в цикле событий, никакие другие задачи не могут выполняться в том же потоке. Когда задача выполняет ожидание выражение, работающая задача приостанавливается, и цикл обработки событий выполняет следующую задачу».

https://docs.python.org/3/library/asyncio-dev.html#concurrency-and-multithreading

И я сделал этот уродливый пример:

import asyncio

async def print_message(message):
    print(message)

async def int_sum(a, b):
    await print_message('start_sum')
    result = a + b
    await print_message('end_sum')
    return result

async def int_mul(a, b):
    await print_message('start_mul')
    result = a * b
    await print_message('end_mul')
    return result

async def main():
    result = await asyncio.gather(int_sum(4, 3), int_mul(4, 3))
    print(result)

asyncio.run(main())

С "последовательными" результатами:

$ python async_test.py

start_sum
end_sum
start_mul
end_mul
[7, 12]

Но мне нужен вывод, похожий на коррутину:

$ python async_test.py

start_sum
start_mul
end_sum
end_mul
[7, 12]

Как я могу это сделать?

Примечание: я не ищу пример asyncio.sleep(n), я ищу «Когда задача выполняет выражение ожидания, работающая задача приостанавливается, и цикл обработки событий выполняет следующую задачу».

если вы хотите, чтобы задачи выполнялись параллельно, то вам нужно multiprocessing, а не asyncio...

D.L 30.09.2022 20:59

Примечание. Я не ищу пример «asyncio.sleep(n)», я ищу «Когда задача выполняет выражение ожидания, работающая задача приостанавливается, а цикл событий выполняет следующую задачу». .... но это цель asyncio !

D.L 30.09.2022 21:01

@D.L спасибо за комментарий. Да, я знаю, это цель asyncio, может быть, я не понимаю вывод. Я ожидаю вывод, например: "start_sum # первая задача ожидания ; start_mul # затем ожидание второй задачи ; end_sum # следующая задача первой очереди ; end_mul # Наконец следующая задача ожидания второй очереди ". Но этого не происходит, и я не понимаю, почему.

Rubén 30.09.2022 21:14

у вас есть три варианта cmmon: multiprocessing, threading и asyncio.... если вы хотите, чтобы они были независимыми, то это multiprocessing. Альтернативами являются threading и asyncio, которые похожи на потоки, управляемые пользователем. Так что это зависит от того, что вы хотите сделать ..... надеюсь, что это полезно.

D.L 30.09.2022 21:17
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
2
4
94
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Дело в том, что задачи возвращают управление циклу событий только с оператором yield. В вашем примере у вас уже есть три активных задачи (добавьте asyncio.all_tasks() в первую строку int_sum сопрограммы для подтверждения1), но, например, int_sum не сотрудничает. он не возвращает управление циклу событий. Почему ? Потому что у тебя нет yield.

Простое решение этой проблемы — изменить print_message на:

async def print_message(message):
    print(message)
    await asyncio.sleep(0)

если вы видите исходный код asyncio.sleep:

async def sleep(delay, result=None):
    """Coroutine that completes after a given time (in seconds)."""
    if delay <= 0:
        await __sleep0()
        return result
...

А это тело __sleep0()(прямо над sleep):

@types.coroutine
def __sleep0():
    """Skip one event loop run cycle.

    This is a private helper for 'asyncio.sleep()', used
    when the 'delay' is set to 0.  It uses a bare 'yield'
    expression (which Task.__step knows how to handle)
    instead of creating a Future object.
    """
    yield

Теперь ваш вывод должен быть:

start_sum
start_mul
end_sum
end_mul
[7, 12]

1 Примечание: у вас есть три задачи, asyncio.gather сделает это за вас:

Если какой-либо ожидаемый в aws является сопрограммой, он автоматически запланирован как Задача.

Спасибо за ответ! Я сделал функцию "@types.coroutine; def next_task(): yield" и использовал ее в print_message просто для проверки. Но есть ли смысл сна или «следующей_задачи» в производственном коде? Знаете ли вы, что это плохая практика? Спасибо

Rubén 01.10.2022 09:52

@Rubén Нет Это не считается плохой практикой, когда вам нужно заставить асинхронную функцию передать управление циклу событий, это допустимый вариант. Но не создавайте себе дополнительный next_task, он вам просто не нужен, он уже определен в asyncio framework.

S.B 01.10.2022 11:07

Другие вопросы по теме