Виклик асинхронного методу в Python?


178

Мені було цікаво, чи є в Python будь-яка бібліотека для асинхронних викликів методів . Було б чудово, якби ви могли зробити щось подібне

@async
def longComputation():
    <code>


token = longComputation()
token.registerCallback(callback_function)
# alternative, polling
while not token.finished():
    doSomethingElse()
    if token.finished():
        result = token.result()

Або викликати несинхронну процедуру асинхронно

def longComputation()
    <code>

token = asynccall(longComputation())

Було б чудово мати більш вдосконалену стратегію як рідну в мовному ядрі. Чи вважалося це?


Станом на Python 3.4: docs.python.org/3/library/asyncio.html (є перенести для 3.3 і новенького asyncі awaitсинтаксису від 3.5).
jonrsharpe

Не існує механізму зворотного виклику, але ви можете об'єднати результати в словник, і він заснований на багатопроцесорному модулі Python. Я впевнений, що ви можете додати ще один параметр до оформленої функції як зворотний дзвінок. github.com/alex-sherman/deco .
RajaRaviVarma

Для початку. Офіційна документація - docs.python.org/3/library/concurrency.html
Адарш Мадреча

Відповіді:


141

Можна використовувати багатопроцесорний модуль, доданий в Python 2.6. Ви можете використовувати пули процесів, а потім отримувати результати асинхронно з:

apply_async(func[, args[, kwds[, callback]]])

Наприклад:

from multiprocessing import Pool

def f(x):
    return x*x

if __name__ == '__main__':
    pool = Pool(processes=1)              # Start a worker processes.
    result = pool.apply_async(f, [10], callback) # Evaluate "f(10)" asynchronously calling callback when finished.

Це лише одна альтернатива. Цей модуль пропонує безліч можливостей для досягнення того, що ви хочете. Також зробити це декоративником буде дуже просто.


5
Лукас С., ваш приклад не працює, на жаль. Функція зворотного дзвінка ніколи не викликається.
DataGreed

6
Напевно, варто пам’ятати, що це породжує окремі процеси, а не окремі потоки в процесі. Це може мати певні наслідки.
користувач47741

11
Це працює: результат = pool.apply_async (f, [10], зворотний виклик = фініш)
MJ

6
Щоб справді зробити що-небудь асинхронно в python, потрібно використовувати багатопроцесорний модуль для нерестування нових процесів. Просто створення нових потоків все ще належить до глобального блоку інтерпретаторів, який запобігає виконанню пітоном кількох речей одночасно.
Дракар

2
Якщо ви не хочете породити новий процес під час використання цього рішення - змініть імпорт на from multiprocessing.dummy import Pool. multiprocessing.dummy має таку саму поведінку, реалізовану над потоками замість процесів
Almog Cohen

203

Щось на зразок:

import threading

thr = threading.Thread(target=foo, args=(), kwargs={})
thr.start() # Will run "foo"
....
thr.is_alive() # Will return whether foo is running currently
....
thr.join() # Will wait till "foo" is done

Детальнішу інформацію див. У документації за адресою https://docs.python.org/library/threading.html .


1
так, якщо вам просто потрібно робити речі асинхронно, то чому б просто не використовувати нитку? адже нитка - це легка вага, ніж процес
kk1957

22
Важлива примітка: стандартна реалізація (CPython) потоків не допоможе при обчисленні завдань, пов'язаних із "глобальним блокуванням інтерпретатора". Дивіться бібліотечний документ: посилання
solublefish

3
Чи користування thread.join () справді асинхронним? Що робити, якщо ви хочете не блокувати потік (наприклад, потік інтерфейсу) та не використовувати тону ресурсів, роблячи певний цикл на ньому?
Mgamerz

1
@Mgamerz приєднання синхронне. Ви можете дозволити потоку помістити результати виконання в якусь чергу, або / і викликати зворотний виклик. Інакше ви не знаєте, коли це зроблено (якщо взагалі).
Дракоша

1
Чи можна викликати функцію зворотного виклику наприкінці виконання потоку, як це можна зробити при багатопроцесорній роботі.
Пул

49

Як і в Python 3.5, ви можете використовувати розширені генератори для функцій асинхронізації.

import asyncio
import datetime

Розширений синтаксис генератора:

@asyncio.coroutine
def display_date(loop):
    end_time = loop.time() + 5.0
    while True:
        print(datetime.datetime.now())
        if (loop.time() + 1.0) >= end_time:
            break
        yield from asyncio.sleep(1)


loop = asyncio.get_event_loop()
# Blocking call which returns when the display_date() coroutine is done
loop.run_until_complete(display_date(loop))
loop.close()

Новий async/awaitсинтаксис:

async def display_date(loop):
    end_time = loop.time() + 5.0
    while True:
        print(datetime.datetime.now())
        if (loop.time() + 1.0) >= end_time:
            break
        await asyncio.sleep(1)


loop = asyncio.get_event_loop()
# Blocking call which returns when the display_date() coroutine is done
loop.run_until_complete(display_date(loop))
loop.close()

8
@carnabeh, чи можете ви розширити цей приклад, щоб включити функцію "def longComputation ()" ОП? Більшість прикладів використовує "await asyncio.sleep (1)", але якщо longComputation () повертається, скажімо, подвійним, ви не можете просто використовувати "очікувати longComputation ()".
Фаб

Десять років у майбутньому, і це має бути прийнятою відповіддю зараз. Коли ви говорите про async в python3.5 +, то, що вам спадає на думку, повинні бути ключові слова asyncio та async.
zeh

31

Це не в мовному ядрі, а дуже зріла бібліотека, яка робить те, що хочеться, перекручене . Він представляє об'єкт Deferred, до якого можна приєднати зворотні дзвінки або оброблювачі помилок ("помилки"). Відкладене - це, в основному, «обіцянка», що функція зрештою матиме результат.


1
Зокрема, подивіться на twisted.internet.defer ( twistedmatrix.com/documents/8.2.0/api/… ).
Ніколас Райлі

21

Ви можете реалізувати декоратор, щоб зробити ваші функції асинхронними, хоча це трохи хитро. multiprocessingМодуль повна менших примх і , здавалося б , довільних обмежень - все більше підстав , щоб инкапсулировать його за дружній інтерфейс, хоча.

from inspect import getmodule
from multiprocessing import Pool


def async(decorated):
    r'''Wraps a top-level function around an asynchronous dispatcher.

        when the decorated function is called, a task is submitted to a
        process pool, and a future object is returned, providing access to an
        eventual return value.

        The future object has a blocking get() method to access the task
        result: it will return immediately if the job is already done, or block
        until it completes.

        This decorator won't work on methods, due to limitations in Python's
        pickling machinery (in principle methods could be made pickleable, but
        good luck on that).
    '''
    # Keeps the original function visible from the module global namespace,
    # under a name consistent to its __name__ attribute. This is necessary for
    # the multiprocessing pickling machinery to work properly.
    module = getmodule(decorated)
    decorated.__name__ += '_original'
    setattr(module, decorated.__name__, decorated)

    def send(*args, **opts):
        return async.pool.apply_async(decorated, args, opts)

    return send

Код нижче ілюструє використання декоратора:

@async
def printsum(uid, values):
    summed = 0
    for value in values:
        summed += value

    print("Worker %i: sum value is %i" % (uid, summed))

    return (uid, summed)


if __name__ == '__main__':
    from random import sample

    # The process pool must be created inside __main__.
    async.pool = Pool(4)

    p = range(0, 1000)
    results = []
    for i in range(4):
        result = printsum(i, sample(p, 100))
        results.append(result)

    for result in results:
        print("Worker %i: sum value is %i" % result.get())

У реальному випадку я б трохи більше розробив питання про декоратор, запропонувавши якийсь спосіб відключити його для налагодження (зберігаючи майбутній інтерфейс на місці), або, можливо, інструмент для боротьби з винятками; але я думаю, що це досить добре демонструє принцип.


Це має бути найкращою відповіддю. Мені подобається, як це може повернути цінність. Не так, як нитка, яка просто працює асинхронно.
Аміна Нурайні

16

Просто

import threading, time

def f():
    print "f started"
    time.sleep(3)
    print "f finished"

threading.Thread(target=f).start()

8

Ви можете використовувати eventlet. Це дозволяє записувати те, що видається синхронним кодом, але змушує його працювати асинхронно по мережі.

Ось приклад супер мінімального сканера:

urls = ["http://www.google.com/intl/en_ALL/images/logo.gif",
     "https://wiki.secondlife.com/w/images/secondlife.jpg",
     "http://us.i1.yimg.com/us.yimg.com/i/ww/beta/y3.gif"]

import eventlet
from eventlet.green import urllib2

def fetch(url):

  return urllib2.urlopen(url).read()

pool = eventlet.GreenPool()

for body in pool.imap(fetch, urls):
  print "got body", len(body)

7

Моє рішення:

import threading

class TimeoutError(RuntimeError):
    pass

class AsyncCall(object):
    def __init__(self, fnc, callback = None):
        self.Callable = fnc
        self.Callback = callback

    def __call__(self, *args, **kwargs):
        self.Thread = threading.Thread(target = self.run, name = self.Callable.__name__, args = args, kwargs = kwargs)
        self.Thread.start()
        return self

    def wait(self, timeout = None):
        self.Thread.join(timeout)
        if self.Thread.isAlive():
            raise TimeoutError()
        else:
            return self.Result

    def run(self, *args, **kwargs):
        self.Result = self.Callable(*args, **kwargs)
        if self.Callback:
            self.Callback(self.Result)

class AsyncMethod(object):
    def __init__(self, fnc, callback=None):
        self.Callable = fnc
        self.Callback = callback

    def __call__(self, *args, **kwargs):
        return AsyncCall(self.Callable, self.Callback)(*args, **kwargs)

def Async(fnc = None, callback = None):
    if fnc == None:
        def AddAsyncCallback(fnc):
            return AsyncMethod(fnc, callback)
        return AddAsyncCallback
    else:
        return AsyncMethod(fnc, callback)

І працює саме так, як вимагається:

@Async
def fnc():
    pass

5

Щось подібне для мене працює, ви можете викликати цю функцію, і вона передаватиметься на новий потік.

from thread import start_new_thread

def dowork(asynchronous=True):
    if asynchronous:
        args = (False)
        start_new_thread(dowork,args) #Call itself on a new thread.
    else:
        while True:
            #do something...
            time.sleep(60) #sleep for a minute
    return

2

Чи є якась причина не використовувати нитки? Ви можете використовувати threadingклас. Замість finished()функції використовуйте isAlive(). result()Функція могла join()нитку і отримати результат. І, якщо ви можете, замініть run()і __init__функції, щоб викликати функцію, вказану в конструкторі, і зберегти значення десь до екземпляра класу.


2
Якщо це обчислювально дорога функція, нарізка функцій не дасть вам нічого (це, мабуть, насправді зробить все повільніше), оскільки процес Python обмежений одним ядром процесора через GIL.
Курт

2
@Kurt, хоча це правда, ОП не згадувало, що його стурбованість виступила. Є й інші причини, що хочуть асинхронної поведінки ...
Пітер Хансен

Нитки в python не є великими, коли ви хочете мати можливість вбити виклик асинхронного методу, оскільки лише головний потік python приймає сигнали.
CivFan

2

Ви можете використовувати concurrent.futures (доданий у Python 3.2).

import time
from concurrent.futures import ThreadPoolExecutor


def long_computation(duration):
    for x in range(0, duration):
        print(x)
        time.sleep(1)
    return duration * 2


print('Use polling')
with ThreadPoolExecutor(max_workers=1) as executor:
    future = executor.submit(long_computation, 5)
    while not future.done():
        print('waiting...')
        time.sleep(0.5)

    print(future.result())

print('Use callback')
executor = ThreadPoolExecutor(max_workers=1)
future = executor.submit(long_computation, 5)
future.add_done_callback(lambda f: print(f.result()))

print('waiting for callback')

executor.shutdown(False)  # non-blocking

print('shutdown invoked')

Це дуже чудова відповідь, оскільки це єдиний тут, який дає можливість вивести нитку з зворотними
зворотами

На жаль, це страждає і від "глобального блокування перекладачів". Дивіться бібліотеки Документація: посилання . Тестували з Python 3.7
Алекс

0

Ви можете використовувати процес. Якщо ви хочете запустити його назавжди, використовуйте під час роботи (як мережу):

from multiprocessing import Process
def foo():
    while 1:
        # Do something

p = Process(target = foo)
p.start()

якщо ви просто хочете запустити його один раз, зробіть так:

from multiprocessing import Process
def foo():
    # Do something

p = Process(target = foo)
p.start()
p.join()
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.