Asyncio gerçekte nasıl çalışır?


124

Bu soruyu başka bir sorum motive ediyor: cdef'de nasıl beklenir?

Web'de hakkında tonlarca makale ve blog yazısı var asyncio, ancak hepsi çok yüzeysel. Gerçekte nasıl asynciouygulandığı ve G / Ç'yi eşzamansız yapan şey hakkında herhangi bir bilgi bulamadım . Kaynak kodunu okumaya çalışıyordum, ancak çoğu yardımcı nesnelerle ilgilenen en yüksek dereceli C kodunun binlerce satırı değil, ancak en önemlisi Python sözdizimi ile hangi C kodunu çevireceği arasında bağlantı kurmak zor. içine.

Asycnio'nun kendi belgeleri daha da az yardımcıdır. Orada nasıl çalıştığına dair hiçbir bilgi yok, sadece nasıl kullanılacağına dair bazı yönergeler var, bunlar bazen yanıltıcı / çok kötü yazılmış.

Go'nun coroutine uygulamasına aşinayım ve Python'un da aynı şeyi yapmasını umuyordum. Durum bu olsaydı, yukarıda bağlantısı verilen gönderide bulduğum kod işe yarardı. Olmadığı için şimdi nedenini anlamaya çalışıyorum. Şimdiye kadarki en iyi tahminim aşağıdaki gibidir, lütfen yanlış olduğum yerde beni düzeltin:

  1. Formun prosedür tanımları async def foo(): ..., aslında bir sınıfın miras alma yöntemleri olarak yorumlanır coroutine.
  2. Belki de, async defaslında await, bu yöntemlerin çağrıldığı nesnenin, yürütme sırasında şimdiye kadar kaydettiği ilerlemeyi takip edebildiği ifadelerle birden fazla yönteme bölünmüştür .
  3. Yukarıdakiler doğruysa, esasen, bir eşgüdümün yürütülmesi, bazı genel yönetici (döngü?) Tarafından coroutine nesnesinin yöntemlerini çağırmaya indirgenir.
  4. Global yönetici bir şekilde (nasıl?) G / Ç işlemlerinin Python (yalnızca?) Kodu tarafından gerçekleştirildiğinin farkındadır ve mevcut yürütme yöntemi kontrolden vazgeçtikten sonra yürütmek için bekleyen coroutine yöntemlerinden birini seçebilir ( awaitifadeye ).

Başka bir deyişle, burada bazı asynciosözdizimlerini daha anlaşılır bir şeye "tasfiye etme" girişimim :

async def coro(name):
    print('before', name)
    await asyncio.sleep()
    print('after', name)

asyncio.gather(coro('first'), coro('second'))

# translated from async def coro(name)
class Coro(coroutine):
    def before(self, name):
        print('before', name)

    def after(self, name):
        print('after', name)

    def __init__(self, name):
        self.name = name
        self.parts = self.before, self.after
        self.pos = 0

    def __call__():
        self.parts[self.pos](self.name)
        self.pos += 1

    def done(self):
        return self.pos == len(self.parts)


# translated from asyncio.gather()
class AsyncIOManager:

    def gather(*coros):
        while not every(c.done() for c in coros):
            coro = random.choice(coros)
            coro()

Tahminim doğru çıkarsa: o zaman bir sorunum var. Bu senaryoda G / Ç gerçekte nasıl gerçekleşir? Ayrı bir başlıkta mı? Tercümanın tamamı askıya alındı ​​mı ve G / Ç tercümanın dışında mı gerçekleşiyor? G / Ç ile tam olarak ne kastedilmektedir? Python prosedürüm C prosedürü olarak adlandırılırsa open()ve bu da çekirdeğe kesme gönderirse, kontrolü bırakıp, Python yorumlayıcısı bunu nasıl bilir ve başka bir kodu çalıştırmaya devam ederken, çekirdek kodu gerçek G / Ç işlemini yapar ve kesmeyi orijinal olarak gönderen Python prosedürünü uyandırır? Python yorumlayıcısı prensip olarak bunun farkında nasıl olabilir?


2
Mantığın çoğu olay döngüsü uygulaması tarafından ele alınır. Nasıl CPython bak BaseEventLoopuygulanmaktadır: github.com/python/cpython/blob/...
Blender

@Blender tamam, sanırım sonunda istediğimi buldum, ama şimdi kodun bu şekilde yazıldığını anlamıyorum. Neden _run_onceaslında "özel" yapılan tüm bu modülde sadece kullanışlı fonksiyon olan? Uygulama korkunç ama bu daha az sorun. Neden olay döngüsünde çağırmak isteyeceğiniz tek işlev "beni arama" olarak işaretlenmiştir?
wvxvw

Bu, posta listesi için bir soru. Hangi kullanım durumu _run_onceilk etapta dokunmanızı gerektirir ?
Blender

8
Yine de bu benim soruma cevap vermiyor. Sadece kullanarak herhangi bir yararlı problemi nasıl çözersiniz _run_once? asynciokarmaşıktır ve hataları vardır, ancak lütfen tartışmayı medeni tutun. Kendi anlamadığınız kodun arkasındaki geliştiricilere kötü konuşma.
Blender

1
@ user8371915 Kapsanmadığım bir şey olduğuna inanıyorsanız, cevabıma ekleyebilir veya yorum yapabilirsiniz.
Bharel

Yanıtlar:


206

Asyncio nasıl çalışır?

Bu soruyu cevaplamadan önce, birkaç temel terimi anlamamız gerekir, bunlardan herhangi birini zaten biliyorsanız, bunları atlayın.

Jeneratörler

Üreteçler, bir python işlevinin yürütülmesini askıya almamızı sağlayan nesnelerdir. Kullanıcı tarafından seçilen oluşturucular anahtar kelime kullanılarak gerçekleştirilir yield. yieldAnahtar kelimeyi içeren normal bir işlev oluşturarak , bu işlevi bir oluşturucuya dönüştürürüz:

>>> def test():
...     yield 1
...     yield 2
...
>>> gen = test()
>>> next(gen)
1
>>> next(gen)
2
>>> next(gen)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

Gördüğünüz gibi, oluşturucunun çağrılması next(), yorumlayıcının test çerçevesini yüklemesine ve yielded değerini döndürmesine neden olur . next()Tekrar çağırmak , çerçevenin yorumlayıcı yığınına yeniden yüklenmesine yieldve başka bir değer oluşturmaya devam etmesine neden olur .

Üçüncü kez next()çağrıldığında jeneratörümüz bitmiş ve StopIterationatılmıştır.

Bir jeneratör ile iletişim kurmak

Jeneratörlerin daha az bilinen bir özelliği, onlarla iki yöntem kullanarak iletişim kurabilmenizdir: send()ve throw().

>>> def test():
...     val = yield 1
...     print(val)
...     yield 2
...     yield 3
...
>>> gen = test()
>>> next(gen)
1
>>> gen.send("abc")
abc
2
>>> gen.throw(Exception())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in test
Exception

Arandığında gen.send(), değer yieldanahtar kelimeden bir dönüş değeri olarak iletilir .

gen.throw()Öte yandan, aynı noktada yieldçağrılan istisnalar dışında, jeneratörlerin içinde İstisnaların atılmasına izin verir .

Jeneratörlerden döndürülen değerler

Bir jeneratörden bir değer döndürmek, değerin StopIterationistisnanın içine konmasıyla sonuçlanır . Daha sonra istisnanın değerini geri alabilir ve ihtiyacımız için kullanabiliriz.

>>> def test():
...     yield 1
...     return "abc"
...
>>> gen = test()
>>> next(gen)
1
>>> try:
...     next(gen)
... except StopIteration as exc:
...     print(exc.value)
...
abc

Bakın, yeni bir anahtar kelime: yield from

Python 3.4 yeni bir anahtar kelime eklenmesiyle geldi: yield from. Söz konusu anahtar kelime yapmamızı sağlayan şey, herhangi geçmek olduğunu next(), send()ve throw()bir iç-en iç içe jeneratörüne. İç jeneratör bir değer döndürürse, aynı zamanda şunların dönüş değeridir yield from:

>>> def inner():
...     inner_result = yield 2
...     print('inner', inner_result)
...     return 3
...
>>> def outer():
...     yield 1
...     val = yield from inner()
...     print('outer', val)
...     yield 4
...
>>> gen = outer()
>>> next(gen)
1
>>> next(gen) # Goes inside inner() automatically
2
>>> gen.send("abc")
inner abc
outer 3
4

Bu konuyu daha fazla detaylandırmak için bir makale yazdım .

Hepsini bir araya koy

yield fromPython 3.4'te yeni anahtar kelimeyi sunduktan sonra, jeneratörlerin içinde, tıpkı bir tünel gibi, verileri en içten en dıştaki jeneratörlere ileri geri ileten jeneratörler oluşturabildik. Bu, jeneratörler için yeni bir anlam doğurdu - koroutinler .

Korutinler , çalıştırılırken durdurulabilen ve devam ettirilebilen işlevlerdir. Python'da async defanahtar kelime kullanılarak tanımlanırlar . Çok jeneratörler gibi, onlar da kendi formu kullanın yield fromolan await. Python 3.5'ten önce asyncve awaittanıtılmadan önce , eşgüdümleri, üreteçlerin oluşturulduğu şekilde ( yield fromyerine ile await) yarattık .

async def inner():
    return 1

async def outer():
    await inner()

__iter__()Yöntemi uygulayan her yineleyici veya oluşturucu gibi __await__(), her seferinde devam etmelerine izin veren koroutinler uygulaması await coroçağrılır.

Python belgelerinin içinde kontrol etmeniz gereken güzel bir sıralama diyagramı var .

Asyncio'da, coroutine işlevlerinden ayrı olarak 2 önemli hedefimiz var: görevler ve gelecekler .

Vadeli işlemler

Futures, __await__()yöntemin uygulandığı nesnelerdir ve görevleri belirli bir durumu ve sonucu tutmaktır. Eyalet aşağıdakilerden biri olabilir:

  1. BEKLEMEDE - gelecekte herhangi bir sonuç veya istisna kümesi yok.
  2. İPTAL EDİLDİ - gelecek, kullanılarak iptal edildi fut.cancel()
  3. FINISHED - gelecek, kullanılarak bir sonuç kümesi kullanılarak fut.set_result()veya kullanılarak bir istisna kümesi ile tamamlandıfut.set_exception()

Sonuç, tahmin ettiğiniz gibi, döndürülecek bir Python nesnesi veya ortaya çıkabilecek bir istisna olabilir.

Nesnelerin bir diğer önemli özelliği de future, adı verilen bir yöntemi içermeleridir add_done_callback(). Bu yöntem, işlevlerin görev tamamlanır tamamlanmaz çağrılmasına izin verir - bir istisna oluştursa da bitirse de.

Görevler

Görev nesneleri, eşgüdümlerin etrafını saran ve en içteki ve en dıştaki yordamlarla iletişim kuran özel geleceklerdir. Bir eşdizin awaitgelecek olduğu her seferinde , gelecek göreve geri döner (tıpkı içinde olduğu gibi yield from) ve görev onu alır.

Ardından, görev kendisini geleceğe bağlar. Bunu add_done_callback()geleceği arayarak yapar . Şu andan itibaren, eğer iptal edilerek, bir istisna geçirilerek veya sonuç olarak bir Python nesnesi geçirilerek gelecek gerçekleşecekse, görevin geri çağrısı çağrılacak ve varoluşa geri dönecektir.

Asyncio

Cevaplamamız gereken son önemli soru şu: IO nasıl uygulanıyor?

Asyncio'nun derinliklerinde bir olay döngümüz var. Görevlerin bir olay döngüsü. Olay döngüsünün görevi, her hazır olduklarında görevleri çağırmak ve tüm bu çabayı tek bir çalışan makinede koordine etmektir.

Olay döngüsünün GÇ kısmı, adı verilen tek bir önemli işlev üzerine kurulmuştur select. Seç, altındaki işletim sistemi tarafından uygulanan ve gelen veya giden veriler için soketlerde beklemeye izin veren bir engelleme işlevidir. Veri alındığında uyanır ve veri alan soketleri veya yazmaya hazır olan soketleri döndürür.

Asyncio aracılığıyla bir soket üzerinden veri almaya veya göndermeye çalıştığınızda, aslında aşağıda olan, soketin hemen okunabilen veya gönderilebilen herhangi bir veriye sahip olup olmadığının kontrol edilmesidir. Onun ise .send()tampon dolu veya .recv()tampon boştur, soket kayıtlı select(basitçe, listelerinden birine ekleyerek fonksiyonu rlistiçin recvve wlistiçin send) ve uygun fonksiyon awaitsa yeni oluşturulan futurebu sokete bağlı, nesneyi.

Mevcut tüm görevler gelecek için beklerken, olay döngüsü çağırır selectve bekler. Soketlerden biri gelen veriye sahip olduğunda veya sendtamponu boşaldığında, asyncio bu sokete bağlanan gelecekteki nesneyi kontrol eder ve tamamlandı olarak ayarlar.

Şimdi tüm sihir gerçekleşir. Gelecek tamamlandı, daha önce kendini ekleyen görev add_done_callback()hayata geri dönüyor ve .send()en içteki koroutini ( awaitzincir nedeniyle ) sürdüren koroutini çağırıyor ve yeni alınan verileri yakındaki bir tampondan okuyorsunuz. üzerine döküldü.

Aşağıdaki durumlarda tekrar yöntem zinciri recv():

  1. select.select bekler.
  2. Verilerle birlikte hazır bir soket döndürülür.
  3. Soketten gelen veriler bir arabelleğe taşınır.
  4. future.set_result() denir.
  5. Kendini ekleyen görev add_done_callback()artık uyandırılmıştır.
  6. Görev .send(), en içteki coroutine kadar giden ve onu uyandıran coroutine çağırır.
  7. Veriler tampondan okunuyor ve mütevazı kullanıcımıza geri dönüyor.

Özetle, asyncio, işlevlerin duraklatılmasına ve devam ettirilmesine izin veren jeneratör yeteneklerini kullanır. En yield fromiçteki jeneratörden en dıştaki veriye gidip gelmeye izin veren yetenekler kullanır . IO'nun tamamlanmasını beklerken (OS selectişlevini kullanarak ) işlev yürütmeyi durdurmak için bunların tümünü kullanır .

Ve en iyisi? Bir işlev duraklatıldığında, diğeri çalışabilir ve asyncio olan hassas kumaşla araya girebilir.


12
Daha fazla açıklamaya ihtiyacınız varsa, yorum yapmaktan çekinmeyin. Btw, bunu bir blog makalesi olarak mı yoksa stackoverflow'da bir cevap olarak mı yazmam gerektiğinden tam olarak emin değilim. Soru, cevaplanması gereken uzun bir soru.
Bharel

1
Eşzamansız bir sokette, veri göndermeye veya almaya çalışmak önce işletim sistemi arabelleğini kontrol eder. Almaya çalışıyorsanız ve arabellekte veri yoksa, temel alınan alma işlevi Python'da bir istisna olarak yayılacak bir hata değeri döndürür. Gönderme ve tam arabellek ile aynı. İstisna ortaya çıktığında, Python sırayla bu soketleri işlemi askıya alan seçme işlevine gönderir. Ancak asyncio'nun nasıl çalıştığı değil, aynı zamanda işletim sistemine özgü olan seçme ve soketlerin nasıl çalıştığıdır.
Bharel

2
@ user8371915 Her zaman yardım etmek için buradayım :-) Asyncio'yu anlamak için jeneratörlerin, jeneratör iletişiminin ve çalışmasının nasıl çalıştığını bilmeniz gerektiğini unutmayın yield from. Ancak, okuyucunun bunu zaten bilmesi durumunda atlanabilir olduğunu yukarıya not ettim :-) Eklemem gerektiğini düşündüğünüz başka bir şey var mı?
Bharel

2
Asyncio bölümünden önceki şeyler , dilin kendi başına yaptığı tek şey oldukları için belki de en kritik olanlardır. selectI engellenmeyen nasıl olduğu, hem de hak kazanabilirsiniz / O sistemi OS üzerinde çalışmaya çağırır. Gerçek asyncioyapılar ve olay döngüsü, bunlardan oluşturulan uygulama düzeyinde koddur.
MisterMiyagi

3
Bu gönderi Python'da eşzamansız G / Ç'nin omurgası hakkında bilgi içerir. Böyle nazik bir açıklama için teşekkürler.
mjkim

83

Hakkında konuşmak async/awaitve asyncioaynı şey değil. Birincisi temel, düşük seviyeli bir yapıdır (eşgüdümler), daha sonra ise bu yapıları kullanan bir kitaplıktır. Tersine, tek bir nihai cevap yoktur.

Aşağıda, kitaplıkların nasıl async/awaitve asynciobenzeri çalıştığına dair genel bir açıklama yer almaktadır . Yani, üstte başka numaralar da olabilir (var ...) ama siz onları kendiniz oluşturmadıkça önemsizdirler. Böyle bir soruyu sormak zorunda kalmayacak kadar bilginiz yoksa, fark önemsiz olmalıdır.

1. Bir somun kabuğundaki alt yordamlara karşı korutinler

Tıpkı alt yordamlar (işlevler, prosedürler, ...) gibi, eşgörünümler (üreteçler, ...) çağrı yığını ve komut işaretçisinin bir soyutlamasıdır: kod parçalarının çalıştırılmasından oluşan bir yığın vardır ve her biri belirli bir talimattadır.

Ayrımı defkarşı async defaçıklık oluşurdu. Gerçek fark, returnkarşı yield. Bundan awaitveya yield frombireysel aramalardan tüm yığınlara kadar farkı alın.

1.1. Altyordamlar

Bir alt rutin, yerel değişkenleri tutmak için yeni bir yığın seviyesini ve bir sona ulaşmak için talimatlarının tek bir geçişini temsil eder. Bunun gibi bir alt rutin düşünün:

def subfoo(bar):
     qux = 3
     return qux * bar

Çalıştırdığında, bunun anlamı

  1. barve için yığın alanı ayırqux
  2. ilk ifadeyi özyinelemeli olarak yürütün ve sonraki ifadeye atlayın
  3. bir kerede, returndeğerini çağıran yığına itin
  4. yığını (1.) ve komut işaretçisini (2.) temizleyin

Özellikle, 4. bir alt yordamın her zaman aynı durumda başladığı anlamına gelir. İşlevin kendisine özel olan her şey tamamlandığında kaybolur. Sonrasında talimatlar olsa bile bir işleve devam edilemez return.

root -\
  :    \- subfoo --\
  :/--<---return --/
  |
  V

1.2. Kalıcı alt yordamlar olarak korutinler

Bir koroutin, bir alt program gibidir, ancak durumunu bozmadan çıkabilir . Bunun gibi bir eşdizim düşünün:

 def cofoo(bar):
      qux = yield bar  # yield marks a break point
      return qux

Çalıştırdığında, bunun anlamı

  1. barve için yığın alanı ayırqux
  2. ilk ifadeyi özyinelemeli olarak yürütün ve sonraki ifadeye atlayın
    1. bir kerede, yielddeğerini çağıran yığına itin, ancak yığını ve komut işaretçisini saklayın
    2. Çağırdıktan sonra yield, yığını ve yönerge işaretçisini geri yükleyin ve bağımsız değişkenleriqux
  3. bir kerede, returndeğerini çağıran yığına itin
  4. yığını (1.) ve komut işaretçisini (2.) temizleyin

2.1 ve 2.2'nin eklendiğine dikkat edin - bir koroutin önceden tanımlanmış noktalarda askıya alınabilir ve devam ettirilebilir. Bu, bir alt yordamın başka bir alt yordamı çağırırken askıya alınmasına benzer. Aradaki fark, aktif coroutinin, çağıran yığına kesin olarak bağlı olmamasıdır. Bunun yerine, askıya alınmış bir koroutin ayrı, izole bir yığının parçasıdır.

root -\
  :    \- cofoo --\
  :/--<+--yield --/
  |    :
  V    :

Bu, askıya alınmış eşzamanların serbestçe depolanabileceği veya yığınlar arasında taşınabileceği anlamına gelir. Bir eşdizime erişimi olan herhangi bir çağrı yığını onu devam ettirmeye karar verebilir.

1.3. Çağrı yığınını geçmek

Şimdiye kadar, coroutine'imiz sadece çağrı yığınında aşağı gidiyor yield. Bir değişmez aşağı gidebilir ve yukarı ile çağrı yığını returnve (). Tamlık için, eşgüdümler ayrıca çağrı yığınını yukarı çıkarmak için bir mekanizmaya ihtiyaç duyar. Bunun gibi bir eşdizim düşünün:

def wrap():
    yield 'before'
    yield from cofoo()
    yield 'after'

Çalıştırdığınızda, bu, yığını ve komut işaretçisini bir alt yordam gibi hala tahsis ettiği anlamına gelir. Askıya alındığında, bu hala bir alt programı depolamak gibidir.

Ancak her ikisini deyield from yapar . Stack ve yönerge işaretçisini askıya alır ve çalıştırır . Tamamen bitene kadar askıda kalacağını unutmayın . Ne zaman askıya alınırsa veya bir şey gönderilirse, doğrudan çağrı yığınına bağlanır.wrap cofoowrapcofoocofoocofoo

1.4. Coroutines tüm yol boyunca

Oluşturulduğu gibi, yield fromiki kapsamın başka bir ara alana bağlanmasına izin verir. Yinelemeli olarak uygulandığında, bu , yığının üst kısmının yığının altına bağlanabileceği anlamına gelir .

root -\
  :    \-> coro_a -yield-from-> coro_b --\
  :/ <-+------------------------yield ---/
  |    :
  :\ --+-- coro_a.send----------yield ---\
  :                             coro_b <-/

Bunu unutmayın rootve coro_bbirbirinizi tanımayın. Bu, eşgörünümleri geri aramalardan çok daha temiz hale getirir: eşgörünümler, alt yordamlar gibi 1: 1 ilişki üzerine kuruludur. Coroutinler, normal bir çağrı noktasına kadar tüm mevcut yürütme yığınlarını askıya alır ve devam ettirir.

Özellikle, rootdevam ettirilecek keyfi sayıda eşdizine sahip olabilir. Yine de, aynı anda birden fazla devam edemez. Aynı kökün eşzamanlıları eşzamanlıdır ancak paralel değildir!

1.5. Python'un asyncveawait

Açıklama şimdiye kadar açıkça kullandı yieldve yield fromjeneratörlerinin kelime - yatan işlevselliği aynıdır. Yeni Python3.5 sözdizimi asyncve awaitesas olarak netlik için var.

def foo():  # subroutine?
     return None

def foo():  # coroutine?
     yield from foofoo()  # generator? coroutine?

async def foo():  # coroutine!
     await foofoo()  # coroutine!
     return None

async forVe async withsen kıracak çünkü ifadeleri ihtiyaç vardır yield from/awaitBare ile zincirini forve withtablolar.

2. Basit bir olay döngüsünün anatomisi

Tek başına, bir koroutinin kontrolü başka bir koroutine verme kavramı yoktur . Yalnızca bir coroutine yığınının altındaki arayan için kontrol sağlayabilir. Bu arayan, daha sonra başka bir coroutine geçebilir ve onu çalıştırabilir.

Birkaç eşgüdümün bu kök düğümü genellikle bir olay döngüsüdür : askıya alındığında, bir koroutin, devam ettirmek istediği bir olay verir . Sırayla, olay döngüsü bu olayların gerçekleşmesini verimli bir şekilde bekleyebilir. Bu, daha sonra hangi coroutinin çalışacağına veya devam etmeden önce nasıl bekleyeceğine karar vermesine olanak tanır.

Böyle bir tasarım, döngünün anladığı bir dizi önceden tanımlanmış olay olduğunu ima eder. awaitSonunda bir olay olana kadar, birbirlerinden birkaç koroutine await. Bu olay , kontrolü devrederek olay döngüsü ile doğrudan iletişim kurabiliryield .

loop -\
  :    \-> coroutine --await--> event --\
  :/ <-+----------------------- yield --/
  |    :
  |    :  # loop waits for event to happen
  |    :
  :\ --+-- send(reply) -------- yield --\
  :        coroutine <--yield-- event <-/

Önemli olan, coroutine süspansiyonunun olay döngüsünün ve olayların doğrudan iletişim kurmasına izin vermesidir. Ara koroutin yığını, hangi döngünün onu çalıştırdığı veya olayların nasıl çalıştığı hakkında herhangi bir bilgi gerektirmez .

2.1.1. Zaman içindeki olaylar

Ele alınması en basit olay, zamanda bir noktaya ulaşmaktır. Bu aynı zamanda iş parçacığı kodunun temel bir bloğudur: sleepbir koşul doğru olana kadar tekrar tekrar s. Bununla birlikte, normal bir sleepblok yürütme kendi başına - diğer coroutinlerin engellenmemesini istiyoruz. Bunun yerine, olay döngüsüne geçerli coroutine yığınını ne zaman devam ettirmesi gerektiğini söylemek istiyoruz.

2.1.2. Bir Olay Tanımlama

Bir olay, bir enum, bir tür veya başka bir kimlik aracılığıyla tanımlayabileceğimiz bir değerdir. Bunu, hedef süremizi saklayan basit bir sınıfla tanımlayabiliriz. Olay bilgilerini saklamaya ek olarak , awaitbir sınıfa doğrudan izin verebiliriz .

class AsyncSleep:
    """Event to sleep until a point in time"""
    def __init__(self, until: float):
        self.until = until

    # used whenever someone ``await``s an instance of this Event
    def __await__(self):
        # yield this Event to the loop
        yield self

    def __repr__(self):
        return '%s(until=%.1f)' % (self.__class__.__name__, self.until)

Bu sınıf yalnızca olayı depolar - gerçekten nasıl ele alınacağını söylemez.

Tek özel özellik, anahtar kelimenin aradığı __await__şeydir await. Pratik olarak, bir yineleyicidir, ancak normal yineleme makineleri için mevcut değildir.

2.2.1. Bir olay bekleniyor

Artık bir olayımız olduğuna göre, koroutinler buna nasıl tepki veriyor? Biz eşdeğer ifade etmek gerekir sleeptarafından awaitetkinliğimize ing. Neler olup bittiğini daha iyi görmek için, zamanın yarısında iki kez bekleriz:

import time

async def asleep(duration: float):
    """await that ``duration`` seconds pass"""
    await AsyncSleep(time.time() + duration / 2)
    await AsyncSleep(time.time() + duration / 2)

Bu coroutini doğrudan somutlaştırabilir ve çalıştırabiliriz. Bir jeneratöre benzer şekilde, kullanmak bir coroutine.sendsonuca kadar koroutini çalıştırır yield.

coroutine = asleep(100)
while True:
    print(coroutine.send(None))
    time.sleep(0.1)

Bu bize iki AsyncSleepolay verir ve ardından StopIterationkoroutin yapıldığında a. Tek gecikmenin time.sleepdöngüden kaynaklandığına dikkat edin ! Her biri AsyncSleepyalnızca geçerli zamana göre bir fark saklar.

2.2.2. Olay + Uyku

Bu noktada elimizde iki ayrı mekanizma var :

  • AsyncSleep Bir koroutin içinden verilebilen olaylar
  • time.sleep eşgüdümleri etkilemeden bekleyebilen

Özellikle, bu ikisi ortogonaldir: hiçbiri diğerini etkilemez veya tetiklemez. Sonuç olarak, sleepbir gecikmeyi karşılamak için kendi stratejimizi geliştirebiliriz AsyncSleep.

2.3. Saf bir olay döngüsü

Mecbur kalırsak birkaç coroutines o Uyandırılmak istediğinde, her söyleyebilir. Daha sonra, birincisinin devam ettirilmesini isteyene kadar bekleyebiliriz, sonra bir sonrakini vb. Özellikle, her noktada yalnızca hangisinin bir sonraki olduğunu önemsiyoruz .

Bu, basit bir zamanlama sağlar:

  1. eşgüdümleri istenen uyanma zamanına göre sıralayın
  2. uyanmak isteyen ilk kişiyi seç
  3. bu zamana kadar bekle
  4. bu eşdizimi çalıştır
  5. 1'den itibaren tekrarlayın.

Önemsiz bir uygulama herhangi bir gelişmiş kavrama ihtiyaç duymaz. A list, eşdizimleri tarihe göre sıralamayı sağlar. Beklemek normaldir time.sleep. Eşgörünümleri çalıştırmak eskisi gibi çalışır coroutine.send.

def run(*coroutines):
    """Cooperatively run all ``coroutines`` until completion"""
    # store wake-up-time and coroutines
    waiting = [(0, coroutine) for coroutine in coroutines]
    while waiting:
        # 2. pick the first coroutine that wants to wake up
        until, coroutine = waiting.pop(0)
        # 3. wait until this point in time
        time.sleep(max(0.0, until - time.time()))
        # 4. run this coroutine
        try:
            command = coroutine.send(None)
        except StopIteration:
            continue
        # 1. sort coroutines by their desired suspension
        if isinstance(command, AsyncSleep):
            waiting.append((command.until, coroutine))
            waiting.sort(key=lambda item: item[0])

Tabii ki, burada iyileştirme için bolca yer var. Bekleme kuyruğu için bir yığın veya olaylar için bir gönderim tablosu kullanabiliriz. Ayrıca, dönüş değerlerini de alabilir StopIterationve bunları coroutine atayabiliriz. Ancak temel ilke aynı kalır.

2.4. Kooperatif Bekleme

AsyncSleepOlay ve runolay döngü zamanlanmış olaylar tamamen çalışma uygulaması vardır.

async def sleepy(identifier: str = "coroutine", count=5):
    for i in range(count):
        print(identifier, 'step', i + 1, 'at %.2f' % time.time())
        await asleep(0.1)

run(*(sleepy("coroutine %d" % j) for j in range(5)))

Bu, beş eş çizginin her biri arasında işbirliği yaparak geçiş yapar ve her birini 0,1 saniye askıya alır. Olay döngüsü eşzamanlı olsa da işi 2,5 saniye yerine 0,5 saniyede yürütür. Her bir coroutine durumu tutar ve bağımsız olarak hareket eder.

3. G / Ç olay döngüsü

Destekleyen bir olay döngüsü yoklamasleep için uygundur . Bununla birlikte, bir dosya tanıtıcısı üzerinde G / Ç beklemesi daha verimli bir şekilde yapılabilir: işletim sistemi G / Ç uygular ve bu nedenle hangi tanıtıcıların hazır olduğunu bilir. İdeal olarak, bir olay döngüsü açık bir "G / Ç için hazır" olayını desteklemelidir.

3.1. selectçağrı

Python, işletim sistemini okuma G / Ç tutamaçlarını sorgulamak için zaten bir arayüze sahiptir. Okumak veya yazmak için tutamaçlarla çağrıldığında, tutamaçları okumaya veya yazmaya hazır döndürür :

readable, writeable, _ = select.select(rlist, wlist, xlist, timeout)

Örneğin, openyazmak için bir dosya hazırlayabiliriz ve hazır olmasını bekleyebiliriz:

write_target = open('/tmp/foo')
readable, writeable, _ = select.select([], [write_target], [])

Select döndükten sonra writeableaçık dosyamızı içerir.

3.2. Temel G / Ç olayı

AsyncSleepİsteğe benzer şekilde, G / Ç için bir olay tanımlamamız gerekir. Temeldeki selectmantıkla, olay okunabilir bir nesneye, örneğin bir opendosyaya başvurmalıdır . Ek olarak, ne kadar veri okunacağını da saklıyoruz.

class AsyncRead:
    def __init__(self, file, amount=1):
        self.file = file
        self.amount = amount
        self._buffer = ''

    def __await__(self):
        while len(self._buffer) < self.amount:
            yield self
            # we only get here if ``read`` should not block
            self._buffer += self.file.read(1)
        return self._buffer

    def __repr__(self):
        return '%s(file=%s, amount=%d, progress=%d)' % (
            self.__class__.__name__, self.file, self.amount, len(self._buffer)
        )

Olduğu gibi, AsyncSleepçoğunlukla temeldeki sistem çağrısı için gereken verileri depoluyoruz. Bu sefer, __await__istediğimiz okunana kadar birden çok kez devam ettirilebilir amount. Ek olarak, returnsadece devam etmek yerine I / O sonucunu veriyoruz.

3.3. Okuma G / Ç ile bir olay döngüsünü genişletme

Olay döngümüzün temeli hala rundaha önce tanımlanmıştır. Öncelikle okuma isteklerini takip etmemiz gerekiyor. Bu artık sıralı bir program değil, sadece okuma isteklerini eşgüdümlerle eşleştiriyoruz.

# new
waiting_read = {}  # type: Dict[file, coroutine]

Yana select.selectbir zaman aşımı parametre alır, biz yerine kullanabilirsiniz time.sleep.

# old
time.sleep(max(0.0, until - time.time()))
# new
readable, _, _ = select.select(list(reads), [], [])

Bu bize okunabilir tüm dosyaları verir - eğer varsa, karşılık gelen coroutini çalıştırırız. Hiçbiri yoksa, mevcut koroutinimizin çalışması için yeterince uzun süre bekledik.

# new - reschedule waiting coroutine, run readable coroutine
if readable:
    waiting.append((until, coroutine))
    waiting.sort()
    coroutine = waiting_read[readable[0]]

Son olarak, okuma isteklerini gerçekten dinlemeliyiz.

# new
if isinstance(command, AsyncSleep):
    ...
elif isinstance(command, AsyncRead):
    ...

3.4. Bir araya getirmek

Yukarıdakiler biraz basitleştirmedir. Her zaman okuyabiliyorsak, uyku eşgüdümlerini aç bırakmamak için biraz geçiş yapmalıyız. Okumak ya da bekleyecek hiçbir şey olmamasını halletmemiz gerekiyor. Ancak, sonuç yine de 30 LOC'ye uyuyor.

def run(*coroutines):
    """Cooperatively run all ``coroutines`` until completion"""
    waiting_read = {}  # type: Dict[file, coroutine]
    waiting = [(0, coroutine) for coroutine in coroutines]
    while waiting or waiting_read:
        # 2. wait until the next coroutine may run or read ...
        try:
            until, coroutine = waiting.pop(0)
        except IndexError:
            until, coroutine = float('inf'), None
            readable, _, _ = select.select(list(waiting_read), [], [])
        else:
            readable, _, _ = select.select(list(waiting_read), [], [], max(0.0, until - time.time()))
        # ... and select the appropriate one
        if readable and time.time() < until:
            if until and coroutine:
                waiting.append((until, coroutine))
                waiting.sort()
            coroutine = waiting_read.pop(readable[0])
        # 3. run this coroutine
        try:
            command = coroutine.send(None)
        except StopIteration:
            continue
        # 1. sort coroutines by their desired suspension ...
        if isinstance(command, AsyncSleep):
            waiting.append((command.until, coroutine))
            waiting.sort(key=lambda item: item[0])
        # ... or register reads
        elif isinstance(command, AsyncRead):
            waiting_read[command.file] = coroutine

3.5. Kooperatif G / Ç

AsyncSleep, AsyncReadVe runuygulamalar artık uyku ve / veya okuma tamamen işlevseldir. Aynı şekilde sleepy, okumayı test etmek için bir yardımcı tanımlayabiliriz:

async def ready(path, amount=1024*32):
    print('read', path, 'at', '%d' % time.time())
    with open(path, 'rb') as file:
        result = return await AsyncRead(file, amount)
    print('done', path, 'at', '%d' % time.time())
    print('got', len(result), 'B')

run(sleepy('background', 5), ready('/dev/urandom'))

Bunu çalıştırarak, G / Ç'mizin bekleme göreviyle karıştırıldığını görebiliriz:

id background round 1
read /dev/urandom at 1530721148
id background round 2
id background round 3
id background round 4
id background round 5
done /dev/urandom at 1530721148
got 1024 B

4. Engellemesiz G / Ç

G / Ç dosyalar üzerinde kavramını karşısında alırken, bu gibi bir kütüphane için gerçekten uygun değildir asyncio: selectçağrı dosyaları için her zaman döner ve her ikisi de openve readolabilir süresiz bloke . Bu, bir olay döngüsünün tüm koroutinlerini engeller - ki bu kötüdür. Bu gibi kitaplıklar, aiofilestıkanmayan G / Ç ve dosyadaki olayları taklit etmek için iş parçacıkları ve senkronizasyon kullanır.

Bununla birlikte, soketler engellemeyen G / Ç'ye izin verir ve doğal gecikmeleri onu çok daha kritik hale getirir. Bir olay döngüsünde kullanıldığında, veri beklemek ve yeniden denemek, hiçbir şeyi engellemeden sarılabilir.

4.1. Engellemeyen G / Ç olayı

Bizimkine benzer şekilde AsyncRead, soketler için bir askıya alma ve okuma olayı tanımlayabiliriz. Bir dosya almak yerine, bloke olmaması gereken bir soket alıyoruz. Ayrıca, yerine __await__kullanımlarımız .socket.recvfile.read

class AsyncRecv:
    def __init__(self, connection, amount=1, read_buffer=1024):
        assert not connection.getblocking(), 'connection must be non-blocking for async recv'
        self.connection = connection
        self.amount = amount
        self.read_buffer = read_buffer
        self._buffer = b''

    def __await__(self):
        while len(self._buffer) < self.amount:
            try:
                self._buffer += self.connection.recv(self.read_buffer)
            except BlockingIOError:
                yield self
        return self._buffer

    def __repr__(self):
        return '%s(file=%s, amount=%d, progress=%d)' % (
            self.__class__.__name__, self.connection, self.amount, len(self._buffer)
        )

Bunun aksine AsyncRead, __await__gerçekten engellemeyen G / Ç gerçekleştirir. Veriler mevcut olduğunda her zaman okur. Mevcut veri olmadığında, her zaman askıya alınır. Bu, olay döngüsünün yalnızca yararlı işler yaparken engellendiği anlamına gelir.

4.2. Olay döngüsünün engellenmesini kaldırma

Olay döngüsü söz konusu olduğunda, hiçbir şey pek değişmez. Dinlenecek olay dosyalar için olanla aynıdır - hazır olarak işaretlenmiş bir dosya tanımlayıcısı select.

# old
elif isinstance(command, AsyncRead):
    waiting_read[command.file] = coroutine
# new
elif isinstance(command, AsyncRead):
    waiting_read[command.file] = coroutine
elif isinstance(command, AsyncRecv):
    waiting_read[command.connection] = coroutine

Bu noktada, açık olmalı AsyncReadve AsyncRecvaynı tür olaylardır. Değiştirilebilir bir G / Ç bileşenine sahip tek bir olay olarak bunları kolayca yeniden düzenleyebiliriz . Gerçekte, olay döngüsü, eşgüdümler ve olaylar açıkça birbirinden ayrılır bir programlayıcıyı, rastgele ara kodu ve gerçek G / Ç'yi bir .

4.3. Engellemeyen G / Ç'nin çirkin tarafı

Prensip olarak, ne bu noktada yapması gerektiğini mantığını çoğaltmak olduğu readbir şekilde recviçin AsyncRecv. Ancak, bu şimdi çok daha çirkin - işlevler çekirdek içinde engellendiğinde erken dönüşleri halletmelisiniz, ancak kontrolü size vermelisiniz. Örneğin, bir bağlantıyı açmak yerine bir dosyayı açmak çok daha uzundur:

# file
file = open(path, 'rb')
# non-blocking socket
connection = socket.socket()
connection.setblocking(False)
# open without blocking - retry on failure
try:
    connection.connect((url, port))
except BlockingIOError:
    pass

Uzun lafın kısası, geriye kalan birkaç düzine İstisna işleme hattı. Olaylar ve olay döngüsü bu noktada zaten çalışıyor.

id background round 1
read localhost:25000 at 1530783569
read /dev/urandom at 1530783569
done localhost:25000 at 1530783569 got 32768 B
id background round 2
id background round 3
id background round 4
done /dev/urandom at 1530783569 got 4096 B
id background round 5

Ek

Github'daki örnek kod


yield selfAsyncSleep'te kullanmak bana Task got back yieldhata veriyor , bu neden? Asyncio.Futures'taki kodun bunu kullandığını görüyorum. Çıplak verim kullanmak iyi sonuç verir.
Ron Serruya

1
Olay döngüleri genellikle yalnızca kendi olaylarını bekler. Genellikle olayları ve olay döngülerini kitaplıklar arasında karıştıramazsınız; burada gösterilen olaylar yalnızca gösterilen olay döngüsü ile çalışır. Spesifik olarak, asyncio, olay döngüsü için bir sinyal olarak yalnızca Yok (yani bir çıplak verim) kullanır. Olaylar, uyandırmaları kaydetmek için doğrudan olay döngüsü nesnesiyle etkileşime girer.
MisterMiyagi

12

Kişisel corodesugaring ama biraz eksik, kavramsal olarak doğrudur.

awaitkoşulsuz olarak askıya alınmaz, ancak yalnızca bir engelleme çağrısıyla karşılaşırsa. Bir aramanın engellendiğini nasıl anlar? Buna, beklenen kod tarafından karar verilir. Örneğin, beklenebilir bir soket okuma uygulaması şu şekilde çözülebilir:

def read(sock, n):
    # sock must be in non-blocking mode
    try:
        return sock.recv(n)
    except EWOULDBLOCK:
        event_loop.add_reader(sock.fileno, current_task())
        return SUSPEND

Gerçek asyncio'da eşdeğer kod , Futuresihirli değerleri döndürmek yerine a'nın durumunu değiştirir , ancak kavram aynıdır. Jeneratör benzeri bir nesneye uygun şekilde uyarlandığında, yukarıdaki kod awaitdüzenlenebilir.

Arayan tarafında, coroutininiz şunları içerdiğinde:

data = await read(sock, 1024)

Şunlara yakın bir şeye dönüşür:

data = read(sock, 1024)
if data is SUSPEND:
    return SUSPEND
self.pos += 1
self.parts[self.pos](...)

Jeneratörlere aşina kişiler, yukarıdakileri yield fromaskıya alma işlemini otomatik olarak yapan terimlerle açıklama eğilimindedir .

Süspansiyon zinciri, koroutinin askıya alındığını fark eden, onu çalıştırılabilir kümeden çıkaran ve varsa çalıştırılabilir olan eşgüdümleri yürütmeye devam eden olay döngüsüne kadar tüm yol boyunca devam eder. Hiçbir koroutin çalıştırılamazsa, döngü, select()bir koroutinin ilgilendiği bir dosya tanımlayıcı G / Ç için hazır olana kadar bekler . (Olay döngüsü, dosya tanımlayıcıdan eşgüdümlü bir eşlemeye sahiptir.)

Yukarıdaki örnekte, okunabilir select()olan olay döngüsüne bir kez söylendiğinde, çalıştırılabilir kümeye sockyeniden eklenecek coro, böylece askıya alma noktasından devam edilecektir.

Diğer bir deyişle:

  1. Her şey varsayılan olarak aynı iş parçacığında gerçekleşir.

  2. Olay döngüsü, koroutinleri programlamaktan ve bekledikleri her şey (tipik olarak normalde bloke eden bir IO çağrısı veya bir zaman aşımı) hazır olduğunda onları uyandırmaktan sorumludur.

Düzgün sürüş olay döngüleri hakkında fikir edinmek için, Dave Beazley'nin canlı izleyicilerin önünde bir olay döngüsünü sıfırdan kodlamayı gösterdiği bu konuşmasını tavsiye ederim .


Teşekkür ederim, peşinde olduğum şeye daha yakın, ama bu yine de neden async.wait_for()olması gerekeni yapmadığını açıklamıyor ... Neden olay döngüsüne bir geri arama ekleyip anlatmak bu kadar büyük bir sorun? yeni eklediğiniz de dahil olmak üzere ihtiyaç duyduğu birçok geri aramayı işlemek için? Benim hayal kırıklığım asynciokısmen, temel kavramın çok basit olması ve örneğin, Emacs Lisp'in moda sözcükler kullanmadan, çağlar boyunca uygulanmış olmasından kaynaklanıyor ... (yani create-async-processve accept-process-output- ve gereken tek şey bu ... (devamı)
wvxvw

10
@wvxvw Gönderdiğiniz soruyu cevaplamak için elimden gelenin en iyisini yaptım, sadece son paragrafın altı soru içerdiği düşünüldüğünde bu bile mümkün. Ve böylece devam ediyoruz - bu o değilwait_for , yapması gerekeni yapmıyor (yapıyor, beklemeniz gereken bir eşdizidir), beklentilerinizin, sistemin yapmak için tasarlandığı ve uygulandığı ile uyuşmamasıdır. Sanırım olay döngüsü ayrı bir iş parçacığında çalışıyorsa probleminiz asyncio ile eşleştirilebilir, ancak kullanım durumunuzun ayrıntılarını bilmiyorum ve dürüst olmak gerekirse, tutumunuz size yardım etmeyi çok eğlenceli hale getirmiyor.
user4815162342

5
@wvxvw My frustration with asyncio is in part due to the fact that the underlying concept is very simple, and, for example, Emacs Lisp had implementation for ages, without using buzzwords...- Python için buzzwords olmadan bu basit konsepti uygulamaktan hiçbir şey sizi alıkoyamaz o zaman :) Neden bu çirkin asyncio'yu kullanıyorsunuz? Kendinizinkini sıfırdan uygulayın. Örneğin, kendinizinkini oluşturmakla başlayabilirsiniz.async.wait_for() tam olarak yapması gerekeni yapan işlevinizi .
Mikhail Gerasimov

1
@MikhailGerasimov bunun retorik bir soru olduğunu düşünüyorsunuz. Ama senin için gizemi ortadan kaldırmak isterim. Dil, başkalarıyla konuşmak için tasarlanmıştır. Başkalarının hangi dili konuşacaklarını seçemem, konuştukları dilin anlamsız olduğuna inansam bile, yapabileceğim en iyi şey onları ikna etmeye çalışmaktır. Diğer bir deyişle, seçim yapmakta özgür olsaydım, bırakın, başlamak için asla Python'u seçmezdim asyncio. Ama prensip olarak bu benim vereceğim bir karar değil. İçinden çöp dili kullanarak zorlanırlar ediyorum en.wikipedia.org/wiki/Ultimatum_game .
wvxvw

4

Her şey, asyncio'nun ele aldığı iki ana zorluğa indirgeniyor:

  • Tek bir iş parçacığında birden çok G / Ç nasıl gerçekleştirilir?
  • İşbirliğine dayalı çoklu görev nasıl uygulanır?

İlk noktanın cevabı uzun süredir ortalıktaydı ve buna seçme döngüsü deniyor . Python'da, selektörler modülünde uygulanır .

İkinci soru, coroutine kavramı ile ilgilidir , yani yürütmeyi durdurabilen ve daha sonra geri yüklenebilen işlevler. Python'da coroutinler, üreteçler ve ifadesinden elde edilen verim . Eşzamansız / bekleme sözdiziminin arkasında saklanan şey budur .

Bu cevapta daha fazla kaynak .


DÜZENLEME: Gorutinler hakkındaki yorumunuzu ele alırken:

Asyncio'daki bir gorutine en yakın eşdeğer aslında bir coroutin değil, bir görevdir ( belgelerdeki ). Python'da, bir korutin (veya bir jeneratör) olay döngüsü veya G / Ç kavramları hakkında hiçbir şey bilmez. Basitçe, yieldmevcut durumunu korurken kullanımını durdurabilen bir işlevdir , böylece daha sonra geri yüklenebilir. yield fromSözdizimi şeffaf bir şekilde onları zincirleme sağlar.

Şimdi, bir asyncio görevinde, zincirin en altındaki korutin her zaman bir gelecekle . Bu gelecek daha sonra olay döngüsüne doğru fırlar ve iç mekanizmaya entegre olur. Gelecek başka bir iç geri çağırma tarafından yapılmak üzere ayarlandığında, olay döngüsü geleceği koroutine zincirine göndererek görevi geri yükleyebilir.


DÜZENLEME: Gönderinizdeki bazı soruları ele almak:

Bu senaryoda G / Ç gerçekte nasıl gerçekleşir? Ayrı bir başlıkta mı? Tercümanın tamamı askıya alındı ​​mı ve G / Ç tercümanın dışında mı gerçekleşiyor?

Hayır, iş parçacığında hiçbir şey olmuyor. G / Ç her zaman olay döngüsü tarafından, çoğunlukla dosya tanımlayıcıları aracılığıyla yönetilir. Bununla birlikte, bu dosya tanımlayıcıların kaydı genellikle yüksek seviyeli eşgüdümler tarafından gizlenir ve bu da sizin için pis işi yapar.

G / Ç ile tam olarak ne kastedilmektedir? Python yordamım C open () yordamı olarak adlandırdıysa ve bu da çekirdeğe kesme gönderdiyse, denetimi bıraktıysa, Python yorumlayıcısı bunu nasıl biliyor ve başka bir kodu çalıştırmaya devam ederken, çekirdek kodu gerçek I / O ve kesmeyi orijinal olarak gönderen Python prosedürü uyanana kadar? Prensip olarak Python yorumlayıcısı bunun farkında olabilir mi?

Bir G / Ç, engelleyen herhangi bir aramadır. Asyncio'da, tüm G / Ç işlemleri olay döngüsünden geçmelidir, çünkü sizin de söylediğiniz gibi, olay döngüsünün bazı eşzamanlı kodlarda bir engelleme çağrısının gerçekleştirildiğini bilmesinin bir yolu yoktur. Bu open, bir koroutin bağlamında eşzamanlı kullanmamanız gerektiği anlamına gelir . Bunun yerine, .NET Framework'ün asenkron sürümünü sağlayan özel bir kitaplık gibi aiofiles kullanın open.


Coroutinlerin kullanılarak uygulandığını yield fromsöylemek gerçekten bir şey söylemiyor. yield fromsadece bir sözdizimi yapısıdır, bilgisayarların yürütebileceği temel bir yapı taşı değildir. Benzer şekilde, seçme döngüsü için. Evet, Go'daki coroutines de select döngüsünü kullanıyor, ancak yapmaya çalıştığım şey Go'da çalışıyordu, ancak Python'da değil. Neden işe yaramadığını anlamak için daha ayrıntılı cevaplara ihtiyacım var.
wvxvw

Üzgünüm ... hayır, pek değil. "gelecek", "görev", "şeffaf yol", "verim" sadece moda sözcüklerdir, bunlar programlama alanından nesneler değildir. programlamanın değişkenleri, prosedürleri ve yapıları vardır. Öyleyse, "gorutinin bir görev olduğunu" söylemek, sadece bir soru soran dairesel bir ifadedir. Nihayetinde, asynciobenim için neyin işe yaradığına dair bir açıklama, Python sözdiziminin neye çevrildiğini gösteren C koduna indirgenecektir.
wvxvw

Cevabınızın neden sorumu yanıtlamadığını daha fazla açıklamak gerekirse: Sağladığınız tüm bilgilerle, bağlantılı soruda yayınladığım koddan girişimimin neden işe yaramadığına dair hiçbir fikrim yok. Olay döngüsünü bu kodun çalışacağı şekilde yazabileceğimden kesinlikle eminim. Aslında, bir tane yazmam gerekse bir olay döngüsü yazmamın yolu bu olurdu.
wvxvw

7
@wvxvw Katılmıyorum. Bunlar "moda sözcükler" değil, birçok kütüphanede uygulanan üst düzey kavramlardır. Örneğin, bir asyncio görevi, bir gevent greenlet ve bir gorutinin hepsi aynı şeye karşılık gelir: tek bir iş parçacığı içinde aynı anda çalışabilen bir yürütme birimi. Ayrıca python jeneratörlerinin iç işleyişine girmek istemiyorsanız, asyncio'yu anlamak için C'ye hiç gerek olmadığını düşünüyorum.
Vincent

@wvxvw İkinci düzenlememe bakın. Bu, birkaç yanlış anlamayı ortadan kaldırmalıdır.
Vincent
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.