Phương thức gọi không đồng bộ trong Python?


178

Tôi đã tự hỏi nếu có bất kỳ thư viện cho các cuộc gọi phương thức không đồng bộ trong Python . Sẽ thật tuyệt nếu bạn có thể làm một cái gì đó như

@async
def longComputation():
    <code>


token = longComputation()
token.registerCallback(callback_function)
# alternative, polling
while not token.finished():
    doSomethingElse()
    if token.finished():
        result = token.result()

Hoặc để gọi một thói quen không đồng bộ không đồng bộ

def longComputation()
    <code>

token = asynccall(longComputation())

Sẽ thật tuyệt khi có một chiến lược tinh tế hơn như bản địa trong lõi ngôn ngữ. Điều này đã được xem xét?


Kể từ Python 3.4: docs.python.org/3/l Library / asyncio.html (có một cổng sau cho 3.3 và mới asyncvà sáng bóng awaittừ 3,5).
jonrsharpe

Không có cơ chế gọi lại, nhưng bạn có thể tổng hợp kết quả trong một từ điển và nó dựa trên mô đun đa xử lý của Python. Tôi chắc chắn rằng bạn có thể thêm một tham số nữa cho chức năng trang trí dưới dạng gọi lại. github.com/alex-sherman/deco .
RajaRaviVarma

Để bắt đầu. Tài liệu chính thức - docs.python.org/3/library/concurrency.html
Adarsh Madrecha

Câu trả lời:


141

Bạn có thể sử dụng mô đun đa xử lý được thêm vào trong Python 2.6. Bạn có thể sử dụng nhóm quy trình và sau đó nhận kết quả không đồng bộ với:

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

Ví dụ:

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.

Đây chỉ là một thay thế. Mô-đun này cung cấp rất nhiều cơ sở để đạt được những gì bạn muốn. Ngoài ra nó sẽ thực sự dễ dàng để làm một trang trí từ này.


5
Lucas S., ví dụ của bạn không hoạt động, thật không may. Hàm gọi lại không bao giờ được gọi.
DataGreed

6
Có lẽ đáng để nhớ rằng điều này sinh ra các quy trình riêng biệt hơn là tách luồng trong một quy trình. Điều này có thể một số hàm ý.
dùng47741

11
Điều này hoạt động: result = pool.apply_async (f, [10], gọi lại = kết thúc)
MJ

6
Để thực sự làm bất cứ điều gì không đồng bộ trong python, đòi hỏi phải sử dụng mô đun đa xử lý để sinh ra các quy trình mới. Chỉ đơn thuần là tạo các luồng mới vẫn nằm trong tầm kiểm soát của Khóa phiên dịch toàn cầu, điều này ngăn cản quá trình trăn thực hiện nhiều việc cùng một lúc.
Drahkar

2
Trong trường hợp bạn không muốn sinh ra một quy trình mới trong khi sử dụng giải pháp này - hãy thay đổi nhập thành from multiprocessing.dummy import Pool. Multiprocessing.dummy có cùng một hành vi được thực hiện trên các luồng thay vì các quy trình
Almog Cohen

203

Cái gì đó như:

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

Xem tài liệu tại https://docs.python.org/l Library / threading.html để biết thêm chi tiết.


1
yeah, nếu bạn chỉ cần làm mọi thứ một cách không đồng bộ, tại sao không chỉ sử dụng thread? sau khi tất cả các chủ đề có trọng lượng nhẹ hơn quá trình
kk1957

22
Lưu ý quan trọng: việc triển khai tiêu chuẩn (CPython) của các luồng sẽ không giúp với các tác vụ bị ràng buộc tính toán, do "Khóa phiên dịch toàn cầu". Xem tài liệu thư viện: link
cá hòa tan

3
Việc sử dụng thread.join () có thực sự không đồng bộ? Điều gì xảy ra nếu bạn muốn không chặn một luồng (ví dụ như luồng UI) và không sử dụng một tấn tài nguyên đang thực hiện một vòng lặp while trên nó?
Mgamerz

1
@Mgamerz tham gia là đồng bộ. Bạn có thể để luồng xử lý kết quả thực hiện trong một số hàng đợi hoặc / và gọi lại. Nếu không, bạn không biết khi nào nó hoàn thành (nếu có).
Drakosha

1
Có thể gọi hàm gọi lại khi kết thúc thực thi luồng như bạn có thể thực hiện với đa xử
lý.Pool

49

Kể từ Python 3.5, bạn có thể sử dụng các trình tạo nâng cao cho các hàm async.

import asyncio
import datetime

Cú pháp trình tạo nâng cao:

@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/awaitCú pháp mới :

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, bạn có thể mở rộng ví dụ đó để bao gồm hàm "def longComputing ()" của OP không? Hầu hết các ví dụ sử dụng "await asyncio.s ngủ (1)", nhưng nếu longComputing () trả về, giả sử, gấp đôi, bạn không thể chỉ sử dụng "await longComputing ()".
Fab

Mười năm trong tương lai và đây sẽ là câu trả lời được chấp nhận ngay bây giờ. Khi bạn nói về async trong python3.5 + những gì bạn nghĩ đến nên là từ khóa asyncio và async.
Zeh

31

Nó không nằm trong lõi ngôn ngữ, mà là một thư viện rất trưởng thành thực hiện những gì bạn muốn là Twisted . Nó giới thiệu đối tượng Trì hoãn, bạn có thể đính kèm các hàm gọi lại hoặc xử lý lỗi ("errbacks"). Trì hoãn về cơ bản là một "lời hứa" rằng một chức năng sẽ có kết quả cuối cùng.


1
Đặc biệt, nhìn vào twisted.internet.defer ( twistedmatrix.com/documents/8.2.0/api/... ).
Nicholas Riley

21

Bạn có thể triển khai một trình trang trí để làm cho các chức năng của bạn không đồng bộ, mặc dù điều đó hơi khó. Các multiprocessingmô-đun có đầy đủ trong những thói quen ít và hạn chế dường như tùy ý - tất cả các lý do hơn để đóng gói nó đằng sau một giao diện thân thiện, mặc dù.

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

Mã dưới đây minh họa việc sử dụng trang trí:

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

Trong trường hợp thực tế, tôi sẽ tập trung hơn một chút vào trình trang trí, cung cấp một số cách để tắt nó để gỡ lỗi (trong khi vẫn giữ giao diện trong tương lai), hoặc có thể là một phương tiện để xử lý các ngoại lệ; nhưng tôi nghĩ rằng điều này thể hiện nguyên tắc đủ tốt.


Đây phải là câu trả lời tốt nhất. Tôi thích làm thế nào nó có thể trả lại giá trị. Không giống như các chủ đề chỉ chạy không đồng bộ.
Aminah Nuraini

16

Chỉ

import threading, time

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

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

8

Bạn có thể sử dụng eventlet. Nó cho phép bạn viết những gì có vẻ là mã đồng bộ, nhưng để nó hoạt động không đồng bộ qua mạng.

Đây là một ví dụ về trình thu thập thông tin siêu tối thiểu:

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

Giải pháp của tôi là:

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)

Và hoạt động chính xác theo yêu cầu:

@Async
def fnc():
    pass

5

Một cái gì đó như thế này hoạt động với tôi, sau đó bạn có thể gọi hàm và nó sẽ tự gửi đến một luồng mới.

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

Có bất kỳ lý do để không sử dụng chủ đề? Bạn có thể sử dụng threadinglớp. Thay vì finished()sử dụng chức năng isAlive(). Các result()chức năng có thể join()các chủ đề và lấy kết quả. Và, nếu bạn có thể, ghi đè lên run()và các __init__hàm để gọi hàm được chỉ định trong hàm tạo và lưu giá trị ở đâu đó vào thể hiện của lớp.


2
Nếu đó là một luồng chức năng đắt tiền về mặt tính toán sẽ không mang lại cho bạn bất cứ điều gì (nó có thể sẽ khiến mọi thứ thực sự chậm hơn) do quy trình Python bị giới hạn ở một lõi CPU do GIL.
Kurt

2
@Kurt, trong khi đó là sự thật, OP đã không đề cập đến hiệu suất đó là mối quan tâm của anh ấy. Có những lý do khác để muốn hành vi không đồng bộ ...
Peter Hansen

Các luồng trong python không tuyệt vời khi bạn muốn có tùy chọn hủy cuộc gọi phương thức không đồng bộ, vì chỉ luồng chính trong python nhận được tín hiệu.
CivilFan

2

Bạn có thể sử dụng concallel.futures (được thêm vào 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')

Đây là một câu trả lời rất hay, vì nó là người duy nhất ở đây đưa ra khả năng của một chủ đề với các cuộc gọi lại
Reda Drissi

Thật không may, điều này cũng bị "Khóa phiên dịch toàn cầu". Xem tài liệu thư viện: liên kết . Đã thử nghiệm với Python 3.7
Alex

0

Bạn có thể sử dụng quá trình. Nếu bạn muốn chạy nó, hãy sử dụng mãi mãi trong khi (như kết nối mạng) trong chức năng của bạn:

from multiprocessing import Process
def foo():
    while 1:
        # Do something

p = Process(target = foo)
p.start()

nếu bạn chỉ muốn chạy nó một lần, hãy làm như vậy:

from multiprocessing import Process
def foo():
    # Do something

p = Process(target = foo)
p.start()
p.join()
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.