Python'da eşzamansız yöntem çağrısı?


178

Python'da eşzamansız yöntem çağrıları için herhangi bir kütüphane olup olmadığını merak ediyordum . Böyle bir şey yapabilseydin harika olurdu

@async
def longComputation():
    <code>


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

Ya da eşzamansız bir rutini eşzamansız olarak çağırmak için

def longComputation()
    <code>

token = asynccall(longComputation())

Dil çekirdeğinde yerli olarak daha rafine bir stratejiye sahip olmak harika olurdu. Bu dikkate alındı ​​mı?


Python 3.4'ten itibaren : docs.python.org/3/library/asyncio.html (3.3 için bir backport ve 3.5'ten parlak yeni asyncve awaitsözdizimi var).
jonrsharpe

Geri arama mekanizması yoktur, ancak sonuçları sözlükte toplayabilirsiniz ve Python'un çoklu işleme modülüne dayanır. Eminim dekore işlevi için bir geri arama olarak bir parametre daha ekleyebilirsiniz. github.com/alex-sherman/deco .
RajaRaviVarma

Yanıtlar:


141

Python 2.6'ya eklenen çok işlem modülünü kullanabilirsiniz . Süreç havuzlarını kullanabilir ve ardından aşağıdakilerle senkronize olmayan sonuçlar alabilirsiniz:

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

Örneğin:

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.

Bu sadece bir alternatif. Bu modül, istediğinizi gerçekleştirmek için birçok olanak sağlar. Ayrıca bundan bir dekoratör yapmak gerçekten kolay olacak.


5
Lucas S., örneğin maalesef çalışmıyor. Geri arama işlevi hiçbir zaman çağrılmaz.
DataGreed

6
Muhtemelen bu işlemin bir süreç içinde ayrı bir iş parçacığı yerine ayrı iş parçacıkları oluşturduğunu akılda tutmaya değer. Bu bazı çıkarımlar olabilir.
user47741

11
Bu çalışır: sonuç = pool.apply_async (f, [10], geri arama = bitiş)
MJ

6
Python'da gerçekten eşzamansız bir şey yapmak için yeni süreçleri ortaya çıkarmak için çok işlem modülünü kullanmak gerekir. Sadece yeni iş parçacıkları oluşturmak, bir python işleminin aynı anda birden çok şey yapmasını engelleyen Global Tercüman Kilidi'nin merhametindedir.
Drahkar

2
Bu çözümü kullanırken yeni bir işlem oluşturmak istemiyorsanız - içe aktarma değerini değiştirin from multiprocessing.dummy import Pool. multiprocessing.dummy, işlemler yerine iş parçacıkları üzerinde uygulanan davranışla aynıdır
Almog Cohen

203

Gibi bir şey:

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

Daha fazla bilgi için https://docs.python.org/library/threading.html adresindeki belgelere bakın.


1
evet, eğer işleri eşzamansız olarak yapmanız gerekiyorsa, neden sadece ipliği kullanmıyorsunuz? Sonuçta iş parçacığından daha hafif
kk1957 21:09

22
Önemli not: "Global Tercüman Kilidi" nedeniyle evrelerin standart uygulaması (CPython) bilgi işlemine bağlı görevlerde yardımcı olmaz. Kütüphaneye bakın doc: link
çözünür

3
Thread.join () kullanmak gerçekten zaman uyumsuz mu? Bir iş parçacığını (örneğin, bir UI iş parçacığı) engellemek ve üzerinde bir while döngüsü yaparken bir ton kaynak kullanmak istemezseniz ne olur?
Mgamerz

1
@Mgamerz birleşimi senkronize. İş parçacığının yürütme sonuçlarını bir kuyruğa koymasına izin verebilir veya / ve geri arama yapabilirsiniz. Aksi halde ne zaman yapıldığını bilmiyorsunuz (eğer varsa).
Drakosha

1
İş parçacığı yürütme işleminin sonunda, çoklu işlemlerde yaptığınız gibi bir geri arama işlevi çağırmak mümkün mü.
Havuz

49

Python 3.5'ten itibaren, zaman uyumsuz işlevler için gelişmiş jeneratörler kullanabilirsiniz.

import asyncio
import datetime

Geliştirilmiş jeneratör sözdizimi:

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

Yeni async/awaitsözdizimi:

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, bu örneği OP'nin "def longComputation ()" işlevini içerecek şekilde genişletebilir misiniz? Çoğu örnek "await asyncio.sleep (1)" işlevini kullanır, ancak longComputation (), bir çift döndürürse, "await longComputation ()" işlevini kullanamazsınız.
Fab

Gelecek on yıl ve bu şimdi kabul edilen cevap olmalı. Python3.5 + 'da async hakkında konuştuğunuzda aklınıza gelen şey asyncio ve async anahtar sözcüğü olmalıdır.
zeh


21

İşlevlerinizi zaman uyumsuz hale getirmek için bir dekoratör uygulayabilirsiniz, ancak bu biraz zor. multiprocessingModül küçük tuhaflıklar ve görünüşte keyfi kısıtlamalar dolu - gerçi, bir dostu arayüzü arkasında hapsetmek için bir neden daha.

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

Aşağıdaki kod, dekoratörün kullanımını göstermektedir:

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

Gerçek dünyadaki bir durumda, dekoratörü biraz daha ayıklayacağım, hata ayıklamak için (gelecekteki arayüzü yerinde tutarken) veya belki de istisnalarla uğraşmak için bir tesis sağlamak için bir yol sağladım; ama bence bu prensibi yeterince iyi gösteriyor.


Bu en iyi cevap olmalı. Nasıl değer dönebilir seviyorum. Sadece eşzamansız çalışan iplik gibi değil.
Aminah Nuraini

16

Sadece

import threading, time

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

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

8

Olayı kullanabilirsiniz. Senkronize kod olarak görünen şeyi yazmanıza izin verir, ancak ağ üzerinden senkronize olmayan şekilde çalışmasını sağlar.

İşte süper minimal bir paletli örneği:

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

Benim çözümüm:

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)

Ve tam olarak istendiği gibi çalışır:

@Async
def fnc():
    pass

5

Bunun gibi bir şey benim için çalışıyor, daha sonra işlevi çağırabilirsiniz ve kendisini yeni bir iş parçacığına gönderir.

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

İplik kullanmamak için herhangi bir neden var mı? threadingSınıfı kullanabilirsiniz . finished()Fonksiyon yerine isAlive(). result()Fonksiyon olabilir join()iplik ve sonucu almak. Yapabiliyorsanız, yapıcıda belirtilen işlevi çağırmak ve değeri sınıf örneğine bir yere kaydetmek için run()ve __init__işlevlerini geçersiz kılın .


2
Hesaplama açısından pahalı bir işlev ise, bir Python işlemi GIL nedeniyle bir CPU çekirdeği ile sınırlı olduğu için, diş çekme size hiçbir şey getirmez (muhtemelen işleri daha yavaş hale getirir).
Kurt

2
@Kurt, bu doğru olsa da, OP performansın endişesi olduğunu söylemedi. Eşzamansız davranış istemek için başka nedenler de var ...
Peter Hansen

Yalnızca python'daki ana iş parçacığı sinyal aldığından eşzamansız yöntem çağrısını öldürme seçeneğine sahip olmak istediğinizde python'daki konular harika değildir.
CivFan

2

Concurrent.futures kullanabilirsiniz (Python 3.2'de eklenmiştir).

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

Bu çok harika bir cevap, çünkü burada geri çağrıları olan bir threadpool imkanı sunan tek kişi bu
Reda Drissi

Ne yazık ki, bu da "Global Tercüman Kilidi" ne maruz kalmaktadır. Kütüphane doc: linkine bakınız . Python 3.7 ile test edildi
Alex

0

İşlemi kullanabilirsiniz. Çalıştırırken sonsuza kadar kullanmak (ağ gibi) çalıştırmak istiyorsanız:

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

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

sadece bir kez çalıştırmak istiyorsanız, bunu yapın:

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

p = Process(target = foo)
p.start()
p.join()
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.