Асинхронный вызов метода в 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())

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

Ответ 1

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

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

Ответ 2

Что-то вроде:

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.

Ответ 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()

Ответ 4

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

Ответ 5

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

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

Ответ 6

Просто

import threading, time

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

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

Ответ 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

Ответ 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)

Ответ 9

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

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

Ответ 10

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

Ответ 11

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

Ответ 12

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

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