Как запустить несколько функций одновременно в pyTelegramApi?

Рейтинг: 3Ответов: 3Опубликовано: 11.02.2023

Использую библиотеку pyTelegramApi и нужно запустить несколько функций одновременно прямо в телеграм боте. Я нажимаю кнопку и функции запускаются одновременно. У меня сейчас вот так:

if message.text == 'Включить':
    while True:
        f1 = function1(chat_id)
        f2 = function2(chat_id)
        f3 = function3(chat_id)
        bot.send_message(chat_id, text=f'{f1}\n{f2}\n{f3}')

Как видите, функции запускаються последовательно а не паралельно, как это можно сделать?

UPD:

Дополняю, так как не совсем понятно. Бесконечный цикл работает, так как мне нужно чтобы каждое N времени эти функции выполнялись, там идет запрос по api и парсинг информации. Я хочу чтобы эти функции выполнялись не последовательно (так как это занимает длительное время), а паралельно! По итогу каждая функция return значение, которое отправляет бот. Как можно это более менее суразно сделать?

Ответы

▲ 1Принят

Для запуска нескольких функций одновременно в pyTelegramApi вы можете использовать многопоточность. Один из способов - это использование модуля threading.

Например, вы можете создать три потока для выполнения каждой функции и ожидать их завершения с помощью метода join(). Вот пример кода:

import threading

if message.text == 'Включить':
    f1_result = None
    f2_result = None
    f3_result = None

def run_f1():
    global f1_result
    f1_result = function1(chat_id)

def run_f2():
    global f2_result
    f2_result = function2(chat_id)

def run_f3():
    global f3_result
    f3_result = function3(chat_id)

# создаем три потока для выполнения каждой функции
t1 = threading.Thread(target=run_f1)
t2 = threading.Thread(target=run_f2)
t3 = threading.Thread(target=run_f3)

# запускаем каждый поток
t1.start()
t2.start()
t3.start()

# ожидаем завершения всех потоков
t1.join()
t2.join()
t3.join()

# отправляем результаты каждой функции
bot.send_message(chat_id, text=f'{f1_result}\n{f2_result}\n{f3_result}')

Код создает три потока, каждый из которых выполняет одну из функций. После запуска всех потоков они ожидаются с помощью метода join(). Затем результаты каждой функции отправляются в чат с помощью метода bot.send_message(). Обратите внимание, что результаты каждой функции сохраняются в переменных f1_result, f2_result и f3_result, объявленных вне функций, чтобы они были доступны после завершения потоков.

▲ 3

Достаточно воспользоваться async / await:

import asyncio

async def function1(*args):
    await asyncio.sleep(1)
    return f"function1 done: {args}"
    
async def function2(*args):
    await asyncio.sleep(3)
    return f"function2 done: {args}"
    
async def function3(*args):
    return f"function3 done: {args}"

async def main(): 
    return await asyncio.gather(function1(1), function2(2), function3(3))

if __name__ == '__main__':

    import time
    print("Запускаем потоки...")
    t = time.perf_counter()
    res = asyncio.run(main())
    elapsed = time.perf_counter() - t
    print(f"Выполнились за {elapsed:0.2f} сек.", *res, sep="\n" )

Результат выполнения:

Запускаем потоки...
Выполнились за 3.00 сек.
function1 done: (1,)
function2 done: (2,)
function3 done: (3,)

Если приблизить к Вашей задаче, то:

...
async def start_func_wait_answer():
    return await asyncio.gather(function1(1), function2(2), function3(3))
    return group

def main(): 
    #f1 = function1(chat_id)
    #f2 = function2(chat_id)
    #f3 = function3(chat_id)
    res = asyncio.run(start_func_wait_answer())

    #bot.send_message(chat_id, text=f'{f1}\n{f2}\n{f3}')
    print(*res, sep="\n" )

if __name__ == '__main__':

    import time
    print("Запускаем потоки...")
    t = time.perf_counter()

    # if message.text == 'Включить':
    main()

    elapsed = time.perf_counter() - t
    print(f"Выполнились за {elapsed:0.2f} сек.")
▲ 1

В стандартной библиотеке python есть concurrent.futures.ThreadPoolExecutor, который является обёрткой над модулем threading. Он позволяет удобно запускать несколько потоков и получать результаты выполнения функций.

from concurrent.futures import ThreadPoolExecutor
from time import time, sleep


def function1(*args):
    sleep(2)  # Условно ждём ответ от сервера.
    return f"Первый ответ: {args}"


def function2(*args):
    sleep(5)  # Условно долгие вычисления
    return f"Самый долгий ответ: {args}"


def function3(*args):
    return f"Моментальный ответ: {args}"


with ThreadPoolExecutor() as executor:
    while True:
        t = time()
        futures = []
        print("Запускаем потоки...")
        for func in [function1, function2, function3]:
            future = executor.submit(func, 42)
            futures.append(future)

        answers = []
        print("Ждём результаты...")
        for future in futures:
            result = future.result()
            answers.append(result)

        print(f"Готово! Прошло {time()-t:.2f} секунд", *answers, sep="\n")

В консоль мы получаем:

Запускаем потоки...
Ждём результаты...
Готово! Прошло 5.01 секунд
Первый ответ: (42,)
Самый долгий ответ: (42,)
Моментальный ответ: (42,)
Запускаем потоки...
...

Как видим, программы выполнились параллельно в нескольких потоках.


так как мне нужно чтобы каждое N времени эти функции выполнялись

Возможно, вам нужна библиотека sheduler или встроенный sched

В целом вы можете посмотреть в сторону асинхронных библиотек: aiohttp для запросов и aiogram для создании бота. Это не обязательно, но почему бы и нет.