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のバックポートasyncawait3.5からの光沢のある新しい構文)
jonrsharpe

コールバックメカニズムはありませんが、結果を辞書に集約できます。これはPythonのマルチプロセッシングモジュールに基づいています。装飾された関数にコールバックとしてもう1つのパラメーターを追加できると確信しています。github.com/alex-sherman/deco
RajaRaviVarma

始めましょう。公式ドキュメント-docs.python.org/3/library/concurrency.html
Adarsh Madrecha

回答:


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
ルーカスS.、あなたの例は残念ながら機能しません。コールバック関数が呼び出されることはありません。
DataGreed 2009

6
これは、プロセス内の個別のスレッドではなく、個別のプロセスを生成することを覚えておく価値があるでしょう。これにはいくつかの影響があります。
user47741

11
これは機能します。result= pool.apply_async(f、[10]、callback = finish)
MJ

6
Pythonで本当に非同期に何かを行うには、マルチプロセッシングモジュールを使用して新しいプロセスを生成する必要があります。新しいスレッドの作成だけは、Pythonプロセスが一度に複数のことを実行できないようにするグローバルインタープリターロックのなすがままです。
Drahkar、2014

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)は、「グローバルインタープリターロック」のため、計算にバインドされたタスクには役立ちません。ライブラリのドキュメントを参照してください:link
可溶性フィッシュ

3
thread.join()は本当に非同期ですか?スレッド(UIスレッドなど)をブロックせず、whileループを実行する大量のリソースを使用しない場合はどうなりますか?
Mgamerz 2014年

1
@Mgamerz結合は同期です。スレッドに実行結果をキューに入れさせたり、コールバックを呼び出したりすることができます。それ以外の場合は、それがいつ完了したかは(もしあれば)わかりません。
Drakosha 2014年

1
マルチプロセッシングでできるように、スレッド実行の最後にコールバック関数を呼び出すことは可能ですか?
Pool

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、その例を拡張してOPの「def longComputation()」関数を含めることができますか?ほとんどの例では「await asyncio.sleep(1)」を使用していますが、longComputation()がdoubleなどを返す場合、「await longComputation()」だけを使用することはできません。
2017年

10年後、これは今受け入れられる答えになるはずです。python3.5以降でasyncについて話すとき、頭に浮かぶのはasyncioとasyncキーワードです。
ZEH

31

それは言語コアにはありませんが、あなたが望むことを行う非常に成熟したライブラリはTwistedです。コールバックまたはエラーハンドラー(「errbacks」)をアタッチできるDeferredオブジェクトが導入されています。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())

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


これが最良の答えです。値を返す方法が大好きです。単に非同期的に実行されるスレッドとは異なります。
Aminah Nuraini 2016年

16

ただ

import threading, time

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

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

8

イベントレットを使用できます。同期コードのように見えるものを記述できますが、ネットワーク上で非同期に動作します。

これは、極小のクローラーの例です。

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のために1つのCPUコアに制限されているため、計算コストの高い関数の場合、スレッドを実行しても何も起こりません(実際には遅くなる可能性があります)。
カート

2
@カート、それは本当ですが、OPはパフォーマンスが彼の懸念であると述べていませんでした。非同期動作が必要な理由は他にもあります...
Peter Hansen

Pythonのスレッドは、Pythonのメインスレッドのみが信号を受信するため、非同期メソッド呼び出しを強制終了するオプションを必要とする場合には適していません。
CivFan 2016年

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

これは、コールバック付きの
スレッド

残念ながら、これは「グローバルインタープリターロック」の影響も受けます。ライブラリdoc:linkを参照してください。Python 3.7でテスト
Alex

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()
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.