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.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.sleepiçin: onuawait asyncio.sleepveyaawait asyncio.sleep_msile değiştirin.csi.CSI.snapshotiç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
awaiteden 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.