파이썬에서 비동기 메소드 호출?


178

파이썬 에서 비동기 메소드 호출을위한 라이브러리가 있는지 궁금합니다 . 다음과 같은 일을 할 수 있다면 좋을 것입니다

@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의 다중 처리 모듈을 기반으로합니다. 장식 된 함수에 콜백으로 하나 이상의 매개 변수를 추가 할 수 있다고 확신합니다. github.com/alex-sherman/deco .
RajaRaviVarma

답변:


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

6
이것은 프로세스 내에서 별도의 스레드가 아닌 별도의 프로세스를 생성한다는 것을 명심할 가치가 있습니다. 이것은 약간의 의미가있을 수 있습니다.
user47741

11
이것은 작동합니다 : result = pool.apply_async (f, [10], callback = finish)
MJ

6
파이썬에서 비동기식으로 무언가를하려면 멀티 프로세싱 모듈을 사용하여 새로운 프로세스를 생성해야합니다. 새 스레드를 작성하는 것은 여전히 ​​파이썬 프로세스가 한 번에 여러 작업을 수행하지 못하게하는 전역 통역 잠금의 자비입니다.
Drahkar

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
예, 비동기식으로 작업 해야하는 경우 스레드를 사용하지 않는 이유는 무엇입니까? 모든 스레드가 프로세스보다
가벼워진

22
중요 사항 : 스레드의 표준 구현 (CPython)은 "글로벌 인터프리터 잠금"으로 인해 컴퓨팅 바운드 작업에 도움이되지 않습니다. 라이브러리 문서 참조 : link
solublefish

3
thread.join () 사용이 실제로 비동기입니까? 스레드 (예 : UI 스레드)를 차단하지 않고 while 루프를 수행하는 많은 리소스를 사용하지 않으려면 어떻게해야합니까?
Mgamerz 2016 년

1
@Mgamerz join은 동기식입니다. 스레드가 실행 결과를 일부 대기열에 넣거나 콜백을 호출하도록 할 수 있습니다. 그렇지 않으면 당신은 그것이 언제 끝났는지 모른다.
Drakosha

1
그것은 당신이 multiprocessing.Pool로 할 수있는 것처럼 스레드 실행의 끝에서 콜백 함수를 호출 할 수 있습니다
레다 Drissi

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 ()"을 사용할 수 없습니다.
Fab

앞으로 10 년이되었으며 지금은 이것이 정답입니다. python3.5 이상에서 비동기에 대해 이야기 할 때 염두에 두어야 할 것은 asyncio 및 async 키워드입니다.
zeh


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

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()and __init__함수를 재정 의하여 생성자에 지정된 함수를 호출하고 값을 클래스의 인스턴스에 저장합니다.


2
계산 비용이 많이 드는 함수 스레딩이라면 Python 프로세스가 GIL로 인해 하나의 CPU 코어로 제한되기 때문에 아무것도 얻지 못할 것입니다 (실제로 속도가 느려질 것입니다).
커트

2
@ 커트는 사실이지만 OP는 성능이 그의 관심사라고 언급하지 않았다. 비동기 동작을 원하는 다른 이유가 ... 있습니다
피터 한센

파이썬의 스레드는 비동기 메소드 호출을 종료하는 옵션을 원할 때 좋지 않습니다. 파이썬의 주요 스레드 만 신호를 수신하기 때문입니다.
CivFan

2

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

이 콜백와 스레드의 가능성을 제공 여기에 유일 이것은 매우 훌륭한 답변입니다
레다 Drissi

불행히도 이것은 "전역 통역사 잠금 장치"로 인해 어려움을 겪습니다. 라이브러리 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.