Как запускать параллельно выполняющиеся процессы в python c помощью asyncio или других библиотек?

Я хочу реализовать выполнение двух бесконечных процессов параллельно с помощью Asyncio. Я попробовал следующее:

import asyncio

async def f1():
    print("First function...")
    while True:
        pass

async def f2():
    print("Second function...")
    while True:
        pass


ioloop = asyncio.get_event_loop()
tasks = [
    ioloop.create_task(f1()),
    ioloop.create_task(f2())
]
ioloop.run_until_complete(asyncio.wait(tasks))
ioloop.close()

Программа выводит только:

First function

А нужно чтобы:

First function...
Second function...

Как такое реализовать?

Дополнение: Скорее всего мой вопрос был некорректным вместо pass я имел в виду определенное действие а не ожидание. Думаю так будет понятнее:

import asyncio

async def f1():
    print("Starting first function")
    while True: #Этот код работает бесконечно
        print('First code is working...') 

async def f2():
    print("Starting second function")
    while True: #Этот код тоже работает бесконечно, но не мешает первому
        print('Second code is working too...') 


ioloop = asyncio.get_event_loop()
tasks = [
    ioloop.create_task(f1()),
    ioloop.create_task(f2())
]
ioloop.run_until_complete(asyncio.wait(tasks))
ioloop.close()

Будет выводить:

First code is working...
First code is working...
First code is working...
(До бесконечности)

А надо что-то на подобии:

First code is working...
Second code is working too...
First code is working...
Second code is working too...
(До бесконечности)

Возможно я не очень понял суть библиотеки asyncio (Возможно лучше использовать multiprocessing или что-то ещё)


Ответы (4 шт):

Автор решения: Ян Морозов
import asyncio


async def f1():
    print("First function...")
    while True:
        await asyncio.sleep(4)


async def f2():
    print("Second function...")
    while True:
        await asyncio.sleep(4)


ioloop = asyncio.get_event_loop()
tasks = [
    ioloop.create_task(f1()),
    ioloop.create_task(f2())
]
ioloop.run_until_complete(asyncio.wait(tasks))
ioloop.close()

в новых версиях питон Python (≥ 3.7) лучше запускать асинхронные функции так:

async def f1():
    print("First function...")
    await asyncio.sleep(4) # Имитирование работы программы
    print(f'{f1.__name__} Done')


async def f2():
    print("Second function...")
    await asyncio.sleep(4) # Имитирование работы программы
    print(f'{f2.__name__} Done')


async def main():
    await asyncio.gather(f1(), f2())


if __name__ == '__main__':
    asyncio.run(main())

pass блокирующая команда, вместо нее лучше использовать asyncio.sleep как имитирование работы функции. Для одновременного выполнения асинхронных программ нужно добавить их в asyncio.gather(func(), func()...) а затем запустить с помощью asyncio.run

→ Ссылка
Автор решения: ilapro 53

Как я понял из слов @vitdev (спасибо ему огромное) с помощью asincio параллельно можно запустить "только неблокирующий код (это ввод-вывод- запись в файл, работа с сетью..)". Поэтому asincio подойдёт не для любых задач.

P.S @asanisimov объяснил как работает Этот модуль. Всетаки asincio может помочь, просто работает по другому принципу. Спасибо большое @asanisimov

Я нашел решение данного вопроса с помощью модуля threading. Например так:

from threading import Thread

def f1():
    print("Starting first function")
    while True: #Этот код работает бесконечно
        print('First code is working...')

def f2():
    print("Starting second function")
    while True: #Этот код тоже работает бесконечно
        print('Second code is working too...')



class MyThread(Thread):
    """A threading example"""
    
    def __init__(self, name, function): # name - некое имя потока (Можно было и без этого обойтись)
                                        # function - функция, которая будет запущена параллельно основному потоку
        """Инициализация потока"""
        Thread.__init__(self)
        self.name = name
        self.function = function
    
    def run(self):
        """Запуск потока"""
        self.function()

    
def create_threads():
    """Создаем группу потоков"""
    
    name = "Thread 1"
    function = f1
    my_thread1 = MyThread(name, function)
    my_thread1.start()

    name = "Thread 2"
    function = f2
    my_thread2 = MyThread(name, function)
    my_thread2.start()
 
 
if __name__ == "__main__":
    create_threads()

Вывод у этой программы довольно страшный (Не буду вставлять), но это значит что все работает.

Можно заменить print('First code is working...') и print('Second code is working too...') на pass и убедиться, что обе функции запустились:

from threading import Thread

def f1():
    print("Starting first function")
    while True: #Этот код работает бесконечно
        pass

def f2():
    print("Starting second function")
    while True: #Этот код тоже работает бесконечно
        pass



class MyThread(Thread):
    """A threading example"""
    
    def __init__(self, name, function): # name - некое имя потока
                                        # function - функция, которая будет запущена параллельно основному потоку
        """Инициализация потока"""
        Thread.__init__(self)
        self.name = name
        self.function = function
    
    def run(self):
        """Запуск потока"""
        self.function()

    
def create_threads():
    """Создаем группу потоков"""
    
    name = "Thread 1"
    function = f1
    my_thread1 = MyThread(name, function)
    my_thread1.start()

    name = "Thread 2"
    function = f2
    my_thread2 = MyThread(name, function)
    my_thread2.start()
 
 
if __name__ == "__main__":
    create_threads()

Вывод:

Starting first function
Starting second function
→ Ссылка
Автор решения: asanisimov

Возможно я не очень понял суть библиотеки asyncio (Возможно лучше использовать multiprocessing или что-то ещё) Все зависит от вашей задачи, Ваш пример, не работает параллельно, потому что в функциях выполняются блокирующие операции.

Ядро асинхронного приложения - это событийный цикл. В вашем примере, не вдаваясь в детали, ioloop = asyncio.get_event_loop() - вы его получаете, tasks = [ ioloop.create_task(f1()), ioloop.create_task(f2()) ] - создаете задачи для него, ioloop.run_until_complete(asyncio.wait(tasks)) запускаете эти задачи внутри него. Цикл событий выполняет только одну задачу, до тех пор пока не встречает неблокирующую операцию, когда задача Task выполняется с оператором await, то выполняющаяся задача приостанавливается, а цикл обработки событий выполняет следующую задачу. Для вашего примера достаточно добавить asyncio.sleep(0.1) что бы цикл событий мог передавать контекст выполнения между задачами.

import asyncio

async def f1():
    print("First function...")
    while True:  # Этот код работает бесконечно
        await asyncio.sleep(0.1) #-> на этом месте цикл событий переключит выполнение на другую задачу
        print('First code is working...')

async def f2():
    print("Second function...")
    while True:  # Этот код тоже работает бесконечно, но не мешает первому
        await asyncio.sleep(0.1)#-> на этом месте цикл событий переключит выполнение на другую задачу
        print('Second code is working too...')


ioloop = asyncio.get_event_loop()
tasks = [
    ioloop.create_task(f1()),
    ioloop.create_task(f2())
]
ioloop.run_until_complete(asyncio.wait(tasks))
ioloop.close()

Если Вы не используете асинхронные библиотеки в задачах, или другие i/o wait операции ( к примеру открытий файла и чтение из него, тут можно было использовать потоки), а распаралеливание затрагивает только cpu операции, то необходимо использовать процессы multiprocessing. Как пример:

import multiprocessing as mp
def f1():
    print("First function...")
    while True:  # Этот код работает бесконечно
        print('First code is working...')
        time.sleep(0.1)

def f2():
    print("Second function...")
    while True:  # Этот код тоже работает бесконечно, но не мешает первому
        print('Second code is working too...')
        time.sleep(0.1) # это не обязательно

def main(func: callable):
    func()

tasks = [f1, f2]
procs = [mp.Process(target=main, args=(i,)) for i in tasks] # создаем столько процессов, сколько имеем функций
for proc in procs:
    proc.start()

for proc in procs:
    proc.join()

На практике чаще имеется одна функция и много аргументов, и необходимость распаралелить выполнение одной функции. Тогда кол-во аргументов бьется на пачки и передается пачка в каждый процесс, если кол-во процессов < "пачек" то лучше использовать пул процессов (multiprocessing.Pool), так как создание процесса считается тяжелой операцией, а так у Вас заранее будет выделено N процессов в вашем пуле.

→ Ссылка
Автор решения: eri

Если функция асинхронная

async def f1():
    print("Async function...")
    while True:
        await asyncio.sleep(5)

Если функция синхронная

async def f2():
    print("Blocking function...")
    while True:
        await ioloop.run_in_executor(None, time.sleep(5))

Если на функции не блокирующий опрос состояния можно сделать так - await asyncio.sleep(0) выполняется на каждом шаге лупа пока другой таск висит на слове await в конкурентном таске

async def f2():
    print("Check function...")
    while True:
        await asyncio.sleep(0)
        if driver.state == 1:
            ioloop.call_soon(cb)

await asyncio.sleep(0) можно использовать как переключатель между тасками если не беда что они будут блокироваться - например при какой-нибудь математике, когда ещё не выгодно уходить на мультипроцессинг.

Тут колбэк может снова заблокировать луп - так что смотрим выше

→ Ссылка