Асинхронный вызов метода в Python?

мне было интересно, есть ли библиотека для асинхронных вызовов методов в 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())

было бы здорово иметь более утонченную стратегию как родную в языковом ядре. Это рассматривалось?

12 ответов


можно использовать модуль многопроцессорной добавил в 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.

это только одна альтернатива. Этот модуль предоставляет множество возможностей для достижения желаемого. Также из этого будет очень легко сделать декоратора.


что-то типа:

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/2/library/threading.html#module-threading для более подробной информации; этот код должен работать и для Python 3.


начиная с 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()

Это не в языковом ядре, но очень зрелая библиотека, которая делает то, что вы хотите, это Twisted. Он вводит отложенный объект, к которому можно присоединить обратные вызовы или обработчики ошибок ("errbacks"). Отложенный-это в основном "обещание", что функция в конечном итоге будет иметь результат.


вы можете реализовать декоратор, чтобы сделать ваши функции асинхронными, хотя это немного сложно. The 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())

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


просто

import threading, time

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

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

мое решение:

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

вы можете использовать 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)

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

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

есть ли причина не использовать потоки? Вы можете использовать threading класса. Вместо может join() поток и получить результат. И, если можете, переопределите run() и __init__ функции для вызова функции, указанной в конструкторе, и сохранения значения где-либо в экземпляре класса.


можно использовать одновременно.фьючерсы (добавил в 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')

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

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()