8.15. Tuzaklar

Asyncio’yu hoş kılan aynı kalıplar – öncelik kesintisi olmaması, açık await ifadeleri – ona ısıran kendine özgü bir dizi biçim de kazandırır. Bu sayfa, bilinmeye değecek kadar sık ortaya çıkanların kataloğudur.

8.15.1. await etmeyi unutmak

Bir async def fonksiyonunu çağırmak bir eşyordam nesnesi döndürür. Fonksiyonun gövdesini çalıştırmaz. Onu gerçekten yürütmek için eşyordamın await edilmesi veya bir göreve sarılması gerekir:

async def main():
    send_request()              # bug: returns the coroutine, does nothing
    await send_request()        # right: run it to completion
    asyncio.create_task(send_request())  # right: run it concurrently

Hata sessizdir – eşyordam nesnesi oluşturulur, atılır ve hiç yürütülmez. Uygulama, her şey çalışmış gibi devam eder. MicroPython bazen bir eşyordamın hiç await edilmediği uyarısını günlüğe kaydeder; bazen kaydetmez. Fonksiyon çağrısı gibi görünen her çağrı yerinde eksik await ifadeleri için denetim yapın.

8.15.2. await içermeyen sıkı döngüler

Bir döngüde çalışan ve hiç await etmeyen bir eşyordam olay döngüsünü tekeline alır. Döngü çıkana veya kontrolü bırakana kadar başka hiçbir görev ilerlemez:

async def counter():
    n = 0
    while True:
        n += 1               # bug: starves the loop

Çözüm, döngünün içinde bir yield’dir – genellikle await asyncio.sleep_ms(0) – böylece hazır olan diğer görevler çalışma şansı bulur. Hesaplama yoğun işler de bu biçime aittir: yineleme başına yüzlerce milisaniye çalışan bir görüntü işleme döngüsü, programın geri kalanı takılmasın diye yineleme başına en az bir kez kontrolü bırakmalıdır.

8.15.3. CancelledError‘ı yutmak

iptal sayfası bunu zaten ayrıntılı olarak ele aldı. Burada tekrarlıyoruz çünkü “uygulamam kapanmıyor” durumunun en yaygın nedenidir: bir eşyordam temizlik amacıyla asyncio.CancelledError yakalar ve onu yeniden yükseltmeyi unutur. Görev çalışmaya devam eder; iptali isteyen çağıran, görevin bitmesini sonsuza dek bekleyerek asılı kalır. Temizlikten sonra her zaman yeniden yükseltin ya da açık bir except yerine bir try/finally bloğu kullanın.

8.15.4. Await’ler arasında paylaşılan durumu değiştirmek

İş birliğine dayalı zamanlama, bir eşyordamın await’ler arasında CPU’yu yalnızca kendisinin kullandığını garanti eder, ancak await ettiği anda başka bir eşyordam çalışabilir. İki eşyordam aynı veri yapısını await içeren adımlarda değiştirirse, işlemleri yapıyı bozacak şekillerde iç içe geçebilir:

# bug: two tasks running do_work simultaneously can
# interleave around the await and corrupt items
async def do_work():
    n = len(items)
    await asyncio.sleep_ms(0)
    items.append(some_work(n))

Yalnızca tek bir eşyordam içinde await’ler arasında değiştirilen durum için eşzamanlamaya gerek yoktur. Await’ler boyunca değiştirilen ve birden fazla eşyordamdan erişilen durum için kritik bölümü bir Lock ile sarın.

8.15.5. Modül düzeyinde await

await yalnızca bir async def gövdesinin içinde geçerlidir. Onu modül düzeyinde – herhangi bir eşyordamın dışında – yazmak bir sözdizimi hatasıdır:

# bug: not inside an async def
result = await fetch()

Çözüm, işi bir eşyordama koymak ve onu programın asyncio.run() giriş noktasından çağırmaktır.

8.15.6. Birden fazla asyncio.run çağrısı

MicroPython’un tek bir olay döngüsü vardır. asyncio.run()‘ı art arda iki kez çağırmak – biri kurulum, biri ana iş için – yine aynı döngüyü kullanır. Onu çalışan bir eşyordamın içinden çağırmak bir hatadır: döngü zaten çalışıyordur. Her iki durum da en sık, bir betik organik olarak büyüdüğünde ve yazarın yeni işi mevcut main içine katmak yerine daha fazla run() çağrısı ekleyerek onu genişletmeye çalıştığında ortaya çıkar.

8.15.7. Bir kesmeden Event kullanımı

asyncio.Event.set()‘in yalnızca olay döngüsünün içinden çağrılması güvenlidir. Onu bir GPIO kesme işleyicisinden çağırmak bir bozulma tehlikesidir. Bir görevi bir kesmeden uyandırmak için bunun yerine ThreadSafeFlag kullanın – bununla ilgili sayfa bu biçimi kapsar.

8.15.8. Uzun süren eşzamanlı çağrılar

Bir eşyordam, asyncio’nun kendi bekleme ilkellerini await edebilir; çağırdığı başka her şey eşzamanlı olarak çalışır ve geri dönene kadar döngüyü engeller. 200 ms’lik engelleyici bir time.sleep(), boşaltması 80 ms süren bir SD kart yazması, büyük bir JPEG sıkıştırması, bir csi.CSI.snapshot() çağrısı – bunların her biri olay döngüsünü tüm süreleri boyunca tutar. Çözüm çağrıya bağlıdır:

  • time.sleep için: onu await asyncio.sleep veya await asyncio.sleep_ms ile değiştirin.

  • csi.CSI.snapshot için: yakalama sayfasının oluşturduğu async anlık görüntü sarmalayıcısını kullanın.

  • Uzun hesaplamalar için (görüntü işleme, JPEG kodlama): maliyeti kabul edin ya da işi yinelemeler arasında await eden parçalara bölün.

Asyncio, eşzamanlı bir çağrıyı engellemez yapamaz. Yalnızca başka bir şey await ederken bu sırada diğer eşyordamların çalışmasına izin verebilir.