วิธีการแบบอะซิงโครนัสเรียกใช้ใน 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()

หรือเพื่อเรียกรูทีนที่ไม่ใช่ async แบบอะซิงโครนัส

def longComputation()
    <code>

token = asynccall(longComputation())

มันจะเป็นการดีหากมีกลยุทธ์ที่ละเอียดอ่อนกว่าในภาษาหลัก มีการพิจารณาเรื่องนี้หรือไม่?


ในฐานะของ Python 3.4: docs.python.org/3/library/asyncio.html (มี backport สำหรับ 3.3 และเป็นเงาใหม่asyncและawaitไวยากรณ์จาก 3.5)
jonrsharpe

ไม่มีกลไกการโทรกลับ แต่คุณสามารถรวมผลลัพธ์ในพจนานุกรมและขึ้นอยู่กับโมดูลหลายตัวประมวลผลของ Python ฉันแน่ใจว่าคุณสามารถเพิ่มพารามิเตอร์อีกหนึ่งฟังก์ชันการตกแต่งเป็นการโทรกลับ 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
Lucas S. ตัวอย่างของคุณใช้งานไม่ได้โชคไม่ดี ฟังก์ชั่นการโทรกลับไม่เคยได้รับการเรียก
DataGreed

6
อาจเป็นสิ่งที่ควรคำนึงถึงว่ากระบวนการนี้แยกกระบวนการออกมาแทนที่จะแยกเธรดภายในกระบวนการ สิ่งนี้อาจมีความหมาย
user47741

11
ใช้งานได้: result = pool.apply_async (f, [10], callback = finish)
MJ

6
ในการทำอะไรแบบอะซิงโครนัสอย่างแท้จริงในไพ ธ อนนั้นจำเป็นต้องใช้โมดูลมัลติโปรเซสเซอร์เพื่อวางไข่กระบวนการใหม่ การสร้างเธรดใหม่เพียงอย่างเดียวยังอยู่ในความเมตตาของ Global Interpreter Lock ซึ่งป้องกันไม่ให้กระบวนการไพ ธ อนทำงานหลายอย่างพร้อมกัน
Drahkar

2
ในกรณีที่คุณไม่ต้องการที่จะวางไข่กระบวนการใหม่ในขณะที่ใช้วิธีนี้ - from multiprocessing.dummy import Poolเปลี่ยนการนำเข้า multiprocessing.dummy มีพฤติกรรมที่เหมือนกันทุก
ประการ

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) ของเธรดจะไม่ช่วยงานที่คำนวณได้เนื่องจาก "Global Interpreter Lock" ดู Library doc: link
solublefish

3
ใช้ thread.join () แบบอะซิงโครนัสจริงหรือไม่ จะทำอย่างไรถ้าคุณไม่ต้องการบล็อกเธรด (เช่นเธรด UI) และไม่ใช้ทรัพยากรจำนวนมากในการทำลูปในขณะนั้น
Mgamerz

1
@Mgamerz join เป็นแบบซิงโครนัส คุณสามารถให้เธรดใส่ผลลัพธ์ของการดำเนินการในบางคิวหรือ / และเรียกการเรียกกลับ มิฉะนั้นคุณจะไม่รู้ว่ามันเสร็จสิ้นแล้ว (ถ้าเลย)
Drakosha

1
เป็นไปได้ไหมที่จะเรียกใช้ฟังก์ชัน callback ในตอนท้ายของการประมวลผลเธรดเช่นเดียวกับที่คุณสามารถทำกับมัลติ
โพรเซสซิงพูล

49

ตั้งแต่ Python 3.5 คุณสามารถใช้เครื่องกำเนิดไฟฟ้าที่ปรับปรุงแล้วสำหรับฟังก์ชั่น async

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 คุณสามารถขยายตัวอย่างเพื่อรวมฟังก์ชัน "def longComputation ()" ของ OP ได้หรือไม่ ตัวอย่างส่วนใหญ่ใช้ "await asyncio.sleep (1)" แต่ถ้า longComputation () ส่งกลับบอกว่าเป็น double คุณจะไม่สามารถใช้ "await longComputation ()" ได้
Fab

สิบปีข้างหน้าและนี่ควรเป็นคำตอบที่ได้รับการยอมรับในขณะนี้ เมื่อคุณพูดถึง async ใน python3.5 + สิ่งที่ควรคำนึงถึงคือคำสำคัญ asyncio และ async
zeh

31

มันไม่ได้อยู่ในหลักภาษา แต่เป็นผู้ใหญ่มากห้องสมุดที่ไม่สิ่งที่คุณต้องการคือบิด มันแนะนำวัตถุรอการตัดบัญชีซึ่งคุณสามารถแนบการเรียกกลับหรือตัวจัดการข้อผิดพลาด ("errbacks") เพื่อ การรอการตัดบัญชีนั้นเป็น "สัญญา" ที่ฟังก์ชั่นจะมีผลในที่สุด


1
โดยเฉพาะอย่างยิ่งดูที่ twisted.internet.defer ( twistedmatrix.com/documents/8.2.0/api/… )
นิโคลัส Riley

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


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


2
ถ้ามันเป็นฟังก์ชั่นการทำเกลียวที่มีราคาแพงคุณจะไม่ได้อะไรเลย (มันอาจจะทำให้สิ่งต่าง ๆ ช้าลงจริง ๆ ) เนื่องจากกระบวนการ Python นั้น จำกัด ซีพียูหนึ่งคอร์เนื่องจาก GIL
Kurt

2
@Kurt ในขณะที่เป็นจริง OP ไม่ได้พูดถึงการแสดงที่เป็นความกังวลของเขา มีเหตุผลอื่น ๆ ที่ต้องการมีพฤติกรรมที่ไม่ตรงกัน ...
ปีเตอร์แฮนเซน

เธรดในไพ ธ อนไม่ได้ยอดเยี่ยมเมื่อคุณต้องการมีตัวเลือกในการฆ่าการเรียกเมธอดแบบอะซิงโครนัสเนื่องจากเฉพาะเธรดหลักในไพ ธ อนเท่านั้นที่รับสัญญาณ
CivFan

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

นี่เป็นคำตอบที่ยอดเยี่ยมมากเพราะเป็นคำตอบเดียวที่ให้ความเป็นไปได้ของ
Reda Drissi

โชคไม่ดีที่สิ่งนี้ได้รับผลกระทบจาก "Global Interpreter Lock" ดูเอกสารห้องสมุด: การเชื่อมโยง ทดสอบกับ 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.