web-dev-qa-db-ja.com

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

言語コアでネイティブとして、より洗練された戦略を持つことは素晴らしいことです。これは考慮されましたか?

163
Stefano Borini

multiprocessing module Python 2.6で追加されたものを使用できます。プロセスのプールを使用してから、非同期で結果を取得できます。

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

例えば。:

from multiprocessing import Pool

def f(x):
    return x*x

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

これは1つの選択肢にすぎません。このモジュールは、あなたが望むものを達成するための多くの機能を提供します。また、これからデコレータを作るのは本当に簡単です。

133
Lucas S.

何かのようなもの:

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でも機能するはずです。

194
Drakosha

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

それは言語コアにはありませんが、あなたが望むことをする非常に成熟したライブラリは Twisted です。 Deferredオブジェクトを導入し、コールバックまたはエラーハンドラー( "errbacks")をアタッチできます。 Deferredは、基本的に関数が最終的に結果を持つという「約束」です。

30

デコレータを実装して、関数を非同期にすることができますが、少し注意が必要です。 multiprocessingモジュールには、ちょっとした癖や一見arbitrary意的な制限がたくさんあります。しかし、それを友好的なインターフェースの背後にカプセル化するさらに多くの理由があります。

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 __attribute. This is necessary for
    # the multiprocessing pickling machinery to work properly.
    module = getmodule(decorated)
    decorated.__+= '_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 __== '__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())

実際のケースでは、デコレータについてもう少し詳しく説明し、デバッグのために(将来のインターフェースを適切に保ちながら)オフにする方法を提供するか、例外を処理するための機能を提供します。しかし、これは原理を十分に実証していると思います。

19
xperroni

ただ

import threading, time

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

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

私の解決策は次のとおりです。

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
7
Nevyn

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
Raj

このような何かが私のために働いて、あなたはその後、関数を呼び出すことができ、それは新しいスレッドにそれ自身をディスパッチします。

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
5

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')
2
Nan Zhong

スレッドを使用しない理由はありますか? threadingクラスを使用できます。 finished()関数の代わりにisAlive()を使用します。 result()関数は、スレッドをjoin()して、結果を取得できます。そして、可能であれば、run()および__init__関数をオーバーライドして、コンストラクターで指定された関数を呼び出し、値をクラスのインスタンスのどこかに保存します。

2
ondra

プロセスを使用できます。永久に実行したい場合は、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()
0
Keivan