Gelişen yazılım dünyasında, uygulamaların hızı ve yanıt verme yeteneği her zamankinden daha kritik hale geldi. Peki, geleneksel bloklayıcı işlemlerin performansı düşürdüğü durumlarda, Python ile bu zorluğun üstesinden nasıl gelebiliriz? Bu kapsamlı rehber, Python’ın asenkron programlama desenlerini adım adım keşfederken, uygulamalarınızın potansiyelini nasıl maksimize edeceğinizi gösteriyor.
Modern uygulamalar genellikle eşzamanlı olarak birçok görevi yerine getirme ihtiyacı duyar. Bir web sunucusu düşünün; aynı anda yüzlerce isteği işlemesi, veritabanından veri çekmesi veya harici API’lere sorgu göndermesi gerekebilir. Geleneksel, senkron programlama yaklaşımlarında, bir işlem tamamlanana kadar diğer tüm işlemlerin beklemesi gerekir. Bu “bloklama” davranışı, uygulamanın genel yanıt süresini önemli ölçüde yavaşlatabilir ve kullanıcı deneyimini olumsuz etkileyebilir. İşte tam bu noktada asenkron programlama devreye girer.
Asenkron programlama, bir görevin tamamlanmasını beklerken diğer görevleri yürütmeye devam etme yeteneğidir. Özellikle I/O (giriş/çıkış) yoğun işlemler (ağ istekleri, dosya okuma/yazma, veritabanı sorguları) söz konusu olduğunda muazzam bir fark yaratır. Python’da asenkron programlama denilince akla ilk gelen kütüphane asyncio‘dur. asyncio, bir “olay döngüsü” (event loop) etrafında döner ve programınızdaki asenkron görevleri bu döngü aracılığıyla yönetir. Bir görev bir I/O işlemi beklerken, olay döngüsü CPU’yu boşa harcamadan başka bir hazır görevi çalıştırır. Böylece, tek bir iş parçacığında (single thread) eşzamanlılık (concurrency) sağlanmış olur.
Bu yaklaşım, özellikle ağ uygulamaları, yüksek performanslı web sunucuları (FastAPI, Sanic), veritabanı istemcileri (asyncpg, aiosqlite) ve mikroservis mimarileri gibi senaryolarda kritik öneme sahiptir. Python 3.5 ile gelen async ve await anahtar kelimeleri, asenkron kod yazmayı çok daha okunaklı ve yönetilebilir hale getirmiştir. Bu sayede, karmaşık geri çağırma zincirlerinden (callback hell) kaçınılarak, senkron koda benzer bir akışla asenkron işlemler modelleyebiliriz. Performansı artırmanın yanı sıra, kaynak kullanımını optimize etmek ve daha ölçeklenebilir uygulamalar geliştirmek için de asenkron desenler vazgeçilmez bir araçtır.
Uzman İpucu: Asenkron programlama, CPU yoğun işlemler için ideal değildir. Eğer uygulamanız ağır matematiksel hesaplamalar veya karmaşık veri analizleri gibi CPU’yu yoğun kullanan işlemler içeriyorsa, çoklu iş parçacığı (multithreading) veya çoklu işlem (multiprocessing) gibi paralelizm yaklaşımlarını değerlendirmelisiniz. Asenkronizm, daha çok I/O bekleme sürelerini yönetmek için tasarlanmıştır.
Python’da Asenkron Programlamanın Tarihi ve Evrimi
Python’da eşzamanlı programlama kavramları uzun bir geçmişe sahiptir. Başlangıçta, iş parçacıkları (threads) ve işlemler (processes) aracılığıyla eşzamanlılık sağlandı. Ancak, Python’ın Küresel Yorumlayıcı Kilidi (Global Interpreter Lock – GIL) nedeniyle, çoklu iş parçacıkları CPU yoğun görevlerde gerçek paralelizm sağlayamaz; sadece I/O yoğun görevlerde eşzamanlılığı artırabilir. Bu sınırlamalar, daha verimli bir eşzamanlılık modeline olan ihtiyacı ortaya çıkardı.
İlk asenkron girişimler genellikle geri çağırmalar (callbacks) ve jeneratörler üzerine kuruluydu. Python 2.5 ile gelen yield anahtar kelimesi, jeneratörleri bir tür kooperatif çoklu görev (cooperative multitasking) için kullanma imkanı sundu. Daha sonra, Python 3.4 ile birlikte asyncio modülü standart kütüphaneye eklendi. Bu, Python’da asenkron programlama için resmi bir çerçeve sağladı ve olay döngüsü, korutinler (coroutines) ve görevler (tasks) gibi temel yapı taşlarını tanıttı. Ancak, bu dönemde korutinler hala jeneratörler üzerinden yield from sözdizimiyle tanımlanıyordu, bu da okunabilirliği biraz düşürüyordu.
Gerçek devrim, Python 3.5 ile async ve await anahtar kelimelerinin gelmesiyle yaşandı. Bu iki anahtar kelime, korutinleri açıkça tanımlamak ve asenkron fonksiyon çağrılarını bekletmek için özel bir sözdizimi sundu. Bu yeni sözdizimi, asenkron kodu yazmayı ve okumayı çok daha kolay ve sezgisel hale getirdi, onu senkron koda daha çok benzetti. Daha sonraki Python sürümlerinde (özellikle 3.6 ve 3.7), asyncio modülü önemli iyileştirmeler ve yeni özellikler kazandı, örneğin asenkron bağlam yöneticileri (async with) ve asenkron iteratörler (async for). Günümüzde, asyncio ve ona dayalı kütüphaneler (aiohttp, FastAPI, SQLModel vb.) Python’da yüksek performanslı ve ölçeklenebilir asenkron uygulamalar geliştirmek için standart hale gelmiştir.
Adım Adım Uygulama: İlk Asenkron Programınızı Yazmak
Asenkron programlamanın temellerini anlamak için basit bir örnekle başlayalım. Python’da asenkron kod yazmanın ana bileşenleri async def ile tanımlanan korutinler ve bir korutinin tamamlanmasını beklemek için kullanılan await anahtar kelimesidir. Ayrıca, asenkron kodumuzu çalıştırmak için bir olay döngüsüne ihtiyacımız var ve bunu genellikle asyncio.run() fonksiyonuyla sağlarız. İlk olarak, bloklama yapan bir işlemi simüle eden basit bir asenkron fonksiyon yazacağız ve ardından bunu nasıl çalıştıracağımızı göreceğiz. Bu ilk adım, asenkron kodun temel yapısını ve işleyişini kavramanızı sağlayacaktır.
Basit Bir async/await Örneği Nasıl Oluşturulur?
Bir korutin, diğer asenkron işlemleri bekleyebilen (await edebilen) bir fonksiyondur. Klasik bir fonksiyondan farkı, async def ile tanımlanmasıdır. İşte bir bekleme süresini simüle eden basit bir korutin:
import asyncio
import time
async def merhaba_dunya():
"""Basit bir asenkron fonksiyon."""
print("Merhaba")
await asyncio.sleep(1) # 1 saniye bekler (bloklama yapmadan)
print("Dünya!")
async def ana_program():
"""Ana asenkron fonksiyonumuz."""
baslangic = time.perf_counter()
await merhaba_dunya()
bitis = time.perf_counter()
print(f"Program {bitis - baslangic:.2f} saniyede tamamlandı.")
# Asenkron kodu çalıştırmak için asyncio.run() kullanılır.
# Bu fonksiyon, olay döngüsünü başlatır, korutini çalıştırır ve sonra kapatır.
asyncio.run(ana_program())
Yukarıdaki örnekte, merhaba_dunya bir korutindir. İçindeki await asyncio.sleep(1) ifadesi, fonksiyonun bu noktada 1 saniye boyunca bekleyeceğini, ancak bu bekleme süresi boyunca olay döngüsünün başka bir görevi çalıştırabileceğini belirtir. ana_program fonksiyonu da bir korutindir ve merhaba_dunya korutinini await ederek çağırır. En alttaki asyncio.run(ana_program()) çağrısı, tüm asenkron işlemin başlatılmasını sağlar. Çıktı, "Merhaba", 1 saniye sonra "Dünya!" ve ardından toplam geçen süreyi gösterecektir. Tek bir korutin olduğu için toplam süre yaklaşık 1 saniye olacaktır. Burada önemli olan, asyncio.sleep()'in CPU'yu meşgul etmeden bekleme yapmasıdır.
Birden Fazla Asenkron Görevi Aynı Anda Nasıl Yönetiriz?
Asenkron programlamanın gerçek gücü, birden fazla görevi eşzamanlı olarak çalıştırma yeteneğinde yatar. Python'da bu genellikle asyncio.gather() veya asyncio.create_task() ile yapılır. Her iki yöntem de korutinleri birer göreve dönüştürür ve olay döngüsünün onları paralel olarak yürütmesini sağlar.
import asyncio
import time
async def gorev(gorev_adi, gecikme):
"""Belirli bir süre bekleyen ve adını yazdıran asenkron görev."""
print(f"{gorev_adi} başladı.")
await asyncio.sleep(gecikme)
print(f"{gorev_adi} bitti.")
return f"{gorev_adi} tamamlandı."
async def ana_es_zamanli_program():
"""Birden fazla asenkron görevi eşzamanlı olarak başlatan ana fonksiyon."""
baslangic = time.perf_counter()
# Görevleri oluşturma
gorev1 = gorev("Gorev 1", 3)
gorev2 = gorev("Gorev 2", 1)
gorev3 = gorev("Gorev 3", 2)
# asyncio.gather() ile tüm görevleri aynı anda bekleyebiliriz.
# Bu, tüm görevlerin tamamlanmasını bekler ve sonuçlarını liste olarak döndürür.
sonuclar = await asyncio.gather(gorev1, gorev2, gorev3)
bitis = time.perf_counter()
print(f"\nTüm görevler {bitis - baslangic:.2f} saniyede tamamlandı.")
print(f"Sonuçlar: {sonuclar}")
# Programı çalıştır
asyncio.run(ana_es_zamanli_program())
Bu örnekte, üç farklı gorev korutini tanımlıyoruz. Her bir görev, farklı bir gecikme süresine sahiptir. asyncio.gather(gorev1, gorev2, gorev3) kullanarak bu üç görevi aynı anda çalıştırıyoruz. Geleneksel senkron bir programda, bu üç görev toplam 3 + 1 + 2 = 6 saniye sürerdi. Ancak asenkron olarak çalıştırıldıklarında, olay döngüsü hepsini paralel olarak yönetir ve en uzun süren görevin tamamlanması kadar (yani 3 saniye) bir sürede hepsi biter. Çıktıda görevlerin başlangıç ve bitiş zamanlarının iç içe geçtiğini ve toplam sürenin yaklaşık 3 saniye olduğunu göreceksiniz. asyncio.create_task() ise görevleri arka planda başlatır ve size bir Task nesnesi döndürür, bu sayede görevlerinizi daha esnek bir şekilde yönetebilir ve daha sonra await ile sonuçlarını bekleyebilirsiniz.
Uzman İpucu: asyncio.gather(), verilen tüm korutinlerin veya görevlerin tamamlanmasını bekler. Eğer bir görevde hata olursa, diğer görevler çalışmaya devam eder ancak gather() bu hatayı çağrıldığı yere iletir. Hata yönetimini doğru yapmak, kararlı asenkron uygulamalar için hayati önem taşır.
Gerçek Dünya Senaryoları: Python Async ile Performans Nasıl Artırılır?
Asenkron programlama, özellikle I/O yoğun uygulamalarda performans artışı sağlar. Geleneksel senkron yaklaşımlarla bir sunucuya yüzlerce veya binlerce eşzamanlı istek geldiğinde, her isteğin ayrı ayrı işlenmesi kuyruklara ve uzun gecikmelere neden olabilir. Asenkron modeller, bu bekleme sürelerini akıllıca yöneterek sistemin çok daha fazla isteği aynı anda ve verimli bir şekilde işlemesine olanak tanır. Aşağıda, gerçek dünya problemlerini ele alan iki yaygın senaryo üzerinde duracağız: web isteklerini paralel hale getirme ve veritabanı işlemlerini asenkronize etme.
Web İsteklerini Paralel Hale Getirme (Vaka Analizi)
Çoğu web uygulamasında, harici API'lere (örneğin, hava durumu servisleri, ödeme ağ geçitleri, diğer mikroservisler) birden fazla istek göndermek yaygın bir durumdur. Eğer bu istekler senkron olarak yapılırsa, her birinin tamamlanması diğerini bekletir ve toplam yanıt süresi tüm isteklerin toplam süresine eşit olur. Bu, özellikle yüksek gecikmeli ağ ortamlarında ciddi bir performans darboğazı yaratır. aiohttp gibi asenkron HTTP istemcileri kullanarak bu sorunu çözebiliriz.
import asyncio
import aiohttp
import time
async def fetch_url(session, url):
"""Belirli bir URL'den veri çeken asenkron fonksiyon."""
start_time = time.perf_counter()
async with session.get(url) as response:
veri = await response.text()
end_time = time.perf_counter()
print(f"URL: {url} | Boyut: {len(veri)} karakter | Süre: {end_time - start_time:.2f} s")
return len(veri)
async def main_web_requests():
"""Birden fazla web isteğini paralel olarak gönderen ana fonksiyon."""
urls = [
"https://www.google.com",
"https://www.bing.com",
"https://www.yahoo.com",
"https://www.wikipedia.org",
"https://www.python.org"
]
baslangic = time.perf_counter()
async with aiohttp.ClientSession() as session:
# Her URL için bir görev oluştur ve hepsini paralel olarak çalıştır
tasks = [fetch_url(session, url) for url in urls]
await asyncio.gather(*tasks)
bitis = time.perf_counter()
print(f"\nTüm web istekleri {bitis - baslangic:.2f} saniyede tamamlandı.")
# aiohttp ile çalışabilmek için kurulum yapmanız gerekebilir: pip install aiohttp
asyncio.run(main_web_requests())
Bu örnekte, aiohttp.ClientSession kullanarak birden fazla URL'ye asenkron olarak HTTP GET istekleri gönderiyoruz. fetch_url korutini, bir URL'den yanıtı alır ve süreyi kaydeder. main_web_requests fonksiyonunda, her bir URL için bir fetch_url görevi oluşturulur ve asyncio.gather(*tasks) ile tüm bu görevler paralel olarak çalıştırılır. Senkron bir yaklaşımla, her istek bir diğerini bekleyeceği için toplam süre çok daha uzun olurdu. Ancak asenkron yaklaşımla, en uzun süren isteğin tamamlanma süresine yakın bir sürede tüm istekler işlenmiş olur. Bu, uygulamanızın dış bağımlılıklara yaptığı çağrıların genel gecikmesini dramatik bir şekilde azaltır ve kullanıcı deneyimini iyileştirir.
Veritabanı İşlemlerini Asenkronize Etme (Vaka Analizi)
Veritabanı işlemleri (sorgular, eklemeler, güncellemeler) tipik olarak I/O yoğundur ve bu da senkron bir uygulamada performans darboğazlarına yol açabilir. Veritabanı sunucusunun yanıt vermesi beklenirken, Python uygulamanızdaki iş parçacığı bloklanır ve başka bir şey yapamaz. Asenkron veritabanı sürücüleri (örneğin, PostgreSQL için asyncpg, SQLite için aiosqlite veya SQLAlchemy 2.0'ın asenkron motoru) bu bekleme sürelerini verimli bir şekilde yöneterek, uygulamanızın aynı anda birden fazla veritabanı sorgusunu veya işlemini yapabilmesini sağlar.
import asyncio
import aiosqlite # pip install aiosqlite
import time
async def create_table_and_insert(db):
"""Veritabanında tablo oluşturur ve veri ekler."""
await db.execute('''
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY,
name TEXT,
email TEXT
)
''')
await db.execute("INSERT INTO users (name, email) VALUES (?, ?)", ("Alice", "alice@example.com"))
await db.execute("INSERT INTO users (name, email) VALUES (?, ?)", ("Bob", "bob@example.com"))
await db.commit()
print("Tablo oluşturuldu ve veriler eklendi.")
async def fetch_user_data(db, user_id):
"""Belirli bir kullanıcıyı veritabanından çeker."""
start_time = time.perf_counter()
async with db.execute("SELECT name, email FROM users WHERE id = ?", (user_id,)) as cursor:
user = await cursor.fetchone()
end_time = time.perf_counter()
print(f"Kullanıcı {user_id} çekildi: {user} | Süre: {end_time - start_time:.4f} s")
await asyncio.sleep(0.1) # Simule edilmiş ek işlem süresi
return user
async def main_db_operations():
"""Asenkron veritabanı işlemlerini yöneten ana fonksiyon."""
baslangic = time.perf_counter()
async with aiosqlite.connect(":memory:") as db: # Bellek içi veritabanı
await create_table_and_insert(db)
# Paralel sorgular
tasks = [
fetch_user_data(db, 1),
fetch_user_data(db, 2),
fetch_user_data(db, 1) # Aynı kullanıcıyı tekrar çekme
]
sonuclar = await asyncio.gather(*tasks)
print(f"\nVeritabanı sorgu sonuçları: {sonuclar}")
bitis = time.perf_counter()
print(f"Tüm veritabanı işlemleri {bitis - baslangic:.2f} saniyede tamamlandı.")
asyncio.run(main_db_operations())
Bu örnekte, aiosqlite kütüphanesini kullanarak asenkron veritabanı erişimi sağlıyoruz. create_table_and_insert korutini, bir tablo oluşturur ve örnek veriler ekler. fetch_user_data ise belirli bir kullanıcıyı sorgular. main_db_operations içinde, önce bir veritabanı bağlantısı açılır ve tablo oluşturulur, ardından birden fazla fetch_user_data görevi oluşturularak asyncio.gather() ile paralel olarak çalıştırılır. Senkron bir yaklaşımla her sorgu ayrı ayrı zaman alırken, asenkron yaklaşımla sorgular eşzamanlı olarak yürütülür ve genel uygulama yanıt süresi önemli ölçüde kısalır. Bu desen, özellikle yüksek yük altındaki sunucu uygulamalarında veya ETL (Ayıkla, Dönüştür, Yükle) işlemlerinde büyük avantaj sağlar.
Uzman İpucu: Asenkron veritabanı işlemleri yaparken, bağlantı havuzlarını (connection pools) kullanmayı düşünün. Her sorgu için yeni bir bağlantı açmak yerine, önceden açılmış ve yönetilen bir bağlantı havuzundan bağlantı almak performansı artırır ve kaynak yönetimini kolaylaştırır.
| Özellik | Senkron Programlama | Asenkron Programlama |
|---|---|---|
| İşlem Akışı | Adım adım, bir görev bitmeden diğeri başlamaz (bloklayıcı). | Görevin tamamlanmasını beklemeden diğer görevlere geçilebilir (non-bloklayıcı). |
| Kullanım Alanı | CPU yoğun görevler, basit ardışık işlemler. | I/O yoğun görevler (ağ, veritabanı, disk), eşzamanlılık gerektiren uygulamalar. |
| Performans | I/O bekleme süreleri nedeniyle düşük. | I/O bekleme süreleri verimli kullanıldığı için yüksek. |
| Kaynak Kullanımı | Çoklu iş parçacığı/işlem için daha fazla bellek ve CPU overhead'i. | Tek iş parçacığında eşzamanlılık sağladığı için genellikle daha az kaynak. |
| Kompleksite | Genellikle daha basit ve anlaşılması kolay. | Olay döngüsü, korutinler, görevler gibi yeni kavramlar nedeniyle başlangıçta daha karmaşık. |
İleri Düzey Async İpuçları ve Püf Noktaları
Python'ın asenkron yetenekleri async ve await anahtar kelimeleriyle sınırlı değildir. Kütüphane ekosistemi, asenkron bağlam yöneticileri, iteratörler ve hata yönetimi gibi güçlü özellikler sunar. Bu ileri düzey desenler, daha temiz, daha sağlam ve daha okunabilir asenkron kod yazmanıza olanak tanır. Uygulamalarınızın karmaşıklığı arttıkça, bu araçları etkin bir şekilde kullanmak, kod kalitesini ve sürdürülebilirliği doğrudan etkileyecektir.
Context Managers ve Asenkron Iterasyonlar Nasıl Kullanılır?
Geleneksel Python'da, kaynakların (dosyalar, veritabanı bağlantıları) doğru bir şekilde açılıp kapatılmasını sağlamak için with ifadesiyle bağlam yöneticileri kullanılır. Asenkron dünyada da benzer bir ihtiyaç ortaya çıkar ve bu ihtiyacı async with karşılar. Benzer şekilde, büyük veri kümeleri üzerinde döngü yaparken asenkron iteratörler (async for) verimlilik sağlar.
import asyncio
import aiofiles # pip install aiofiles
class AsyncResource:
"""Asenkron bağlam yöneticisi örneği."""
def __init__(self, name):
self.name = name
async def __aenter__(self):
print(f"Kaynak '{self.name}' açılıyor (async with).")
await asyncio.sleep(0.1) # Kaynak açma işlemini simüle et
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
print(f"Kaynak '{self.name}' kapatılıyor (async with).")
await asyncio.sleep(0.1) # Kaynak kapatma işlemini simüle et
if exc_type:
print(f"Hata oluştu: {exc_val}")
async def async_read_file(filename):
"""Asenkron dosya okuma örneği."""
print(f"{filename} okunuyor...")
async with aiofiles.open(filename, mode='r') as f:
content = await f.read()
print(f"{filename} okundu. İçerik uzunluğu: {len(content)}.")
return content
async def async_main_context_and_iter():
# Asenkron bağlam yöneticisi kullanımı
async with AsyncResource("Veritabanı Bağlantısı"):
print("Veritabanı işlemleri yapılıyor...")
await asyncio.sleep(0.2)
# Dosya oluşturma (senkron)
with open("ornek.txt", "w") as f:
f.write("Bu, asenkron okunacak bir metindir.\nİkinci satır.")
# Asenkron dosya okuma ve iterasyon
async with aiofiles.open("ornek.txt", mode='r') as f:
print("\nDosya satır satır okunuyor (async for):")
async for line in f:
print(f"--> {line.strip()}")
asyncio.run(async_main_context_and_iter())
AsyncResource sınıfı, __aenter__ ve __aexit__ özel metodlarını uygulayarak bir asenkron bağlam yöneticisi haline gelir. async with AsyncResource(...) ifadesiyle kaynak açılış ve kapanış süreçleri asenkron olarak yönetilir. Benzer şekilde, aiofiles kütüphanesi sayesinde, dosya işlemlerini (ki bunlar genellikle I/O yoğundur) async with ile asenkron hale getirebiliriz. Ayrıca, büyük dosyaları veya veri akışlarını işlerken async for döngüsü, her bir veri parçasını asenkron olarak okuyarak programın bloklanmamasını sağlar. Bu desenler, kaynak yönetimini ve akış kontrolünü çok daha şık ve verimli bir hale getirir.
Asenkron Kodda Hata Yönetimi ve İptal Mekanizmaları
Herhangi bir karmaşık sistemde olduğu gibi, asenkron uygulamalarda da hatalar meydana gelebilir veya çalışan bir görevin iptal edilmesi gerekebilir. asyncio, bu senaryoları yönetmek için sağlam mekanizmalar sunar. Hata yönetimi için geleneksel try...except blokları geçerlidir, ancak görev iptali gibi durumlarda özel bir yaklaşıma ihtiyaç duyulur.
import asyncio
async def uzun_suren_gorev(gorev_id, gecikme):
"""İptal edilebilir uzun süreli bir görev."""
try:
print(f"[{gorev_id}] Görev başladı.")
await asyncio.sleep(gecikme) # Uzun süren işlemi simüle et
print(f"[{gorev_id}] Görev bitti.")
return f"[{gorev_id}] Başarıyla tamamlandı."
except asyncio.CancelledError:
print(f"[{gorev_id}] Görev İPTAL EDİLDİ!")
raise # İptal sinyalini yukarı taşır
except Exception as e:
print(f"[{gorev_id}] Bir hata oluştu: {e}")
return f"[{gorev_id}] Hata ile bitti: {e}"
async def main_error_and_cancel():
# Hata yönetimi örneği
try:
await uzun_suren_gorev("Hata Testi", 0) # Gecikme 0, hemen bitecek
# await 1/0 # Örneğin burada bir hata fırlatılabilir
except Exception as e:
print(f"\nAna programda hata yakalandı: {e}")
# Görev iptali örneği
task_to_cancel = asyncio.create_task(uzun_suren_gorev("İptal Edilecek Görev", 5))
task_ok = asyncio.create_task(uzun_suren_gorev("Normal Görev", 1))
await asyncio.sleep(0.5) # İptalden önce biraz çalışmasına izin ver
print("\nİptal sinyali gönderiliyor...")
task_to_cancel.cancel() # Görevi iptal et
try:
await task_to_cancel # İptal edilen görevin sonucunu bekleriz
except asyncio.CancelledError:
print(f"İptal edilen görev yakalandı: {task_to_cancel.done()}")
await task_ok # Normal görevin tamamlanmasını bekleriz
print("Tüm işlemler tamamlandı.")
asyncio.run(main_error_and_cancel())
Yukarıdaki örnekte, uzun_suren_gorev korutini, normal çalışmasının yanı sıra asyncio.CancelledError istisnasını yakalamak için bir try...except bloğu içerir. Bir görev task.cancel() ile iptal edildiğinde, await edilen noktalarda (örneğin asyncio.sleep() sırasında) bu istisna fırlatılır. Görev, bu istisnayı yakalayıp temizleme işlemlerini yapabilir ve sonra isterse istisnayı tekrar fırlatarak iptal sinyalini yukarıya taşıyabilir. Bu, kaynak sızıntılarını önlemek ve uygulamanın tutarlı bir durumda kalmasını sağlamak için kritik öneme sahiptir. Ayrıca, asyncio.wait_for() gibi fonksiyonlarla görevlere zaman aşımı (timeout) tanımlayarak, bir görevin belirli bir süre içinde tamamlanmaması durumunda otomatik olarak iptal edilmesini sağlayabiliriz. Bu yaklaşımlar, asenkron uygulamalarınızın daha dirençli ve yönetilebilir olmasını sağlar.
Asenkron Desenler İçin En İyi Uygulamalar ve Tuzaklar
Asenkron programlama, performans ve ölçeklenebilirlik açısından büyük avantajlar sunsa da, yanlış kullanıldığında beklenmedik sorunlara yol açabilir. Etkili ve sağlam asenkron uygulamalar geliştirmek için bazı en iyi uygulamaları takip etmek ve yaygın tuzaklardan kaçınmak önemlidir. Bu bölümde, asenkron kodunuzu optimize etmek ve potansiyel sorunlardan kaçınmak için pratik ipuçlarını ve mobil uyumlu bir HTML örneğini inceleyeceğiz, zira modern uygulamalar genellikle farklı platformlarda kusursuz çalışmalıdır.
Senkron ve Asenkron Kodu Karıştırma Stratejileri
Sıklıkla, bir asenkron uygulamada senkron kütüphaneleri veya bloklayıcı işlemleri kullanma ihtiyacı doğar. Bu durumda doğrudan senkron bir fonksiyonu await edemezsiniz, çünkü bu, olay döngüsünü bloklar ve asenkronizmin faydalarını ortadan kaldırır. asyncio, bu tür senaryolar için run_in_executor() yöntemini sunar.
import asyncio
import time
import requests # Senkron HTTP kütüphanesi
def senkron_islem(veri):
"""Olay döngüsünü bloklayacak senkron bir işlem."""
print(f"Senkron işlem başladı: {veri}")
time.sleep(2) # Uzun süren senkron işlem
print(f"Senkron işlem bitti: {veri}")
return f"Senkron Sonuç: {veri}"
async def asenkron_main_karisik_kod():
print("Asenkron main başladı.")
loop = asyncio.get_running_loop()
# Senkron fonksiyonu bir thread havuzunda çalıştırma
# Bu, olay döngüsünün bloklanmasını engeller.
gorev_senkron = loop.run_in_executor(None, senkron_islem, "veri_1")
gorev_asenkron = asyncio.create_task(asyncio.sleep(1))
gorev_asenkron_2 = asyncio.create_task(asyncio.sleep(0.5))
await asyncio.gather(gorev_senkron, gorev_asenkron, gorev_asenkron_2)
print("Asenkron main bitti.")
async def fetch_sync_url(url):
"""Senkron requests kütüphanesini kullanarak URL çeken fonksiyon."""
print(f"Senkron URL çekiliyor: {url}")
response = requests.get(url)
print(f"Senkron URL çekildi: {url}, Durum: {response.status_code}")
return response.status_code
async def main_with_sync_blocking():
print("\nBloklayıcı senkron çağrılarla test...")
loop = asyncio.get_running_loop()
# requests.get() çağrısı senkron ve bloklayıcıdır.
# Doğrudan await edemeyiz, ancak run_in_executor ile thread havuzunda çalıştırabiliriz.
start = time.time()
task1 = loop.run_in_executor(None, fetch_sync_url, "https://www.google.com")
task2 = loop.run_in_executor(None, fetch_sync_url, "https://www.bing.com")
await asyncio.gather(task1, task2)
end = time.time()
print(f"Bloklayıcı senkron çağrılar tamamlandı: {end - start:.2f} saniye")
# asyncio.run(asenkron_main_karisik_kod())
# asyncio.run(main_with_sync_blocking())
loop.run_in_executor(None, senkron_islem, "veri_1") kodu, senkron_islem fonksiyonunu ayrı bir iş parçacığı veya işlemde çalıştırır (ilk argüman None olduğunda varsayılan bir ThreadPoolExecutor kullanır). Bu sayede, senkron işlemin bloklaması olay döngüsünü etkilemez ve diğer asenkron görevler çalışmaya devam edebilir. Bu strateji, özellikle üçüncü taraf kütüphanelerle çalışırken veya eski senkron kod parçalarını yeniden yazmak yerine entegre etmek istediğinizde çok kullanışlıdır.
Uzman İpucu: run_in_executor() kullanırken, eğer CPU yoğun bir senkron işiniz varsa, ThreadPoolExecutor yerine bir ProcessPoolExecutor kullanmayı düşünebilirsiniz. Bu, GIL'in etkisini aşarak gerçek paralelizm sağlar. Ancak her zaman gerekmeyebilir ve ek karmaşıklık getirebilir.
Mobil Uyumlu HTML ve Asenkron Python İlişkisi
Asenkron Python'ın doğrudan HTML ile bir ilişkisi olmasa da, modern web uygulamaları genellikle hem backend'de (Python) hem de frontend'de (HTML/CSS/JS) performans optimizasyonlarına ihtiyaç duyar. Python asenkron web framework'leri (FastAPI gibi), mobil uyumlu frontend'lere hızlı API yanıtları sağlayarak genel kullanıcı deneyimini iyileştirir. Aşağıdaki CSS kodu, basit bir web sayfasının mobil cihazlarda nasıl daha iyi görüneceğini gösterir. Bu, backend'deki asenkron performansın frontend'deki duyarlı tasarımla birleşerek eksiksiz bir deneyim sunduğunun bir göstergesidir.
Python Async ile Hızlı Web API'ları
Asenkron Python backend'leri, mobil uygulamalarınız için hızlı ve duyarlı API'lar sunar. Bu örnek HTML yapısı, CSS medya sorgularıyla farklı ekran boyutlarına nasıl adapte olunduğunu gösterir.
# FastAPI (asenkron web framework) örneği from fastapi import FastAPI import uvicorn app = FastAPI() @app.get("/items/{item_id}") async def read_item(item_id: int): await asyncio.sleep(0.1) # Simule edilmiş asenkron işlem return {"item_id": item_id, "name": "Async Item"} if __name__ == "__main__": # Bu kısım genellikle bir ASGI sunucusu (uvicorn) tarafından yönetilir. # uvicorn.run(app, host="0.0.0.0", port=8000) print("FastAPI uygulaması hazır. /items/{id} adresine istek gönderebilirsiniz.")Yukarıdaki FastAPI kodu, asenkron bir endpoint'i tanımlar. Bu tür asenkron servisler, mobil uygulamaların hızlı veri almasını ve yanıt vermesini sağlar, böylece kullanıcılar cihazlarından pürüzsüz bir deneyim yaşarlar.
Medya sorguları (
@media), tarayıcının ekran genişliğine göre farklı CSS kurallarının uygulanmasını sağlar. Örneğin,max-width: 768pxaltındaki ekranlar için font boyutları küçültülür, boşluklar ayarlanır ve kod blokları daha okunaklı hale getirilir. Bu teknik, asenkron Python'ın sağladığı backend performansını tamamlayarak, kullanıcıların her cihazda en iyi deneyimi yaşamasını garantiler.Sonuç ve Sıkça Sorulan Sorular
Python'daki asenkron programlama desenleri, özellikle I/O yoğun uygulamalar için geliştiricilere güçlü araçlar sunar.
asynciokütüphanesi veasync/awaitanahtar kelimeleri sayesinde, bloklamayan, eşzamanlı uygulamalar geliştirmek hiç bu kadar kolay olmamıştı. Bu rehberde, temel kavramlardan gerçek dünya senaryolarına, ileri düzey ipuçlarından en iyi uygulamalara kadar asenkron Python dünyasını kapsamlı bir şekilde inceledik. Web isteklerini paralel hale getirmekten veritabanı işlemlerini optimize etmeye kadar birçok alanda performans artışı ve daha iyi kaynak kullanımı sağladığını gördük. Doğru anlaşıldığında ve uygulandığında, asenkron programlama, Python uygulamalarınızın potansiyelini önemli ölçüde artırabilir ve daha ölçeklenebilir, yanıt veren sistemler oluşturmanıza yardımcı olabilir.Sıkça Sorulan Sorular
-
Asenkron Python her zaman senkrondan daha mı hızlıdır?
Hayır, her zaman değil. Asenkron programlama özellikle I/O (giriş/çıkış) yoğun işlemlerde (ağ istekleri, disk okuma/yazma, veritabanı sorguları) performansı artırır, çünkü bir I/O işlemi beklerken diğer görevleri yürütmeye devam edebilir. Ancak, CPU yoğun işlemler için (örneğin, karmaşık matematiksel hesaplamalar) asenkronizmin doğrudan bir performans avantajı yoktur, hatta olay döngüsü yönetimi nedeniyle hafif bir overhead bile olabilir. Bu tür durumlar için çoklu iş parçacığı (multithreading) veya çoklu işlem (multiprocessing) daha uygun olabilir.
-
asyncio.run()veloop.run_until_complete()arasındaki fark nedir?asyncio.run(), Python 3.7 ile tanıtılan ve asenkron bir ana fonksiyonu çalıştırmak için önerilen üst düzey bir fonksiyondur. Bu fonksiyon, bir olay döngüsü oluşturur, belirtilen korutini çalıştırır ve görev tamamlandığında döngüyü düzgün bir şekilde kapatır.loop.run_until_complete()ise daha düşük seviyeli bir API'dir ve genellikle manuel olarak bir olay döngüsü oluşturmanız ve kapatmanız gerekir.asyncio.run()çoğu kullanım durumu için tercih edilen, daha basit ve güvenli bir yaklaşımdır. -
Asenkron Python'da deadlock (kilitlenme) oluşabilir mi?
Evet, asenkron Python'da da deadlock benzeri durumlar veya yarış koşulları (race conditions) oluşabilir, ancak geleneksel iş parçacığı tabanlı deadlock'lardan farklıdır. Genellikle, olay döngüsünü uzun süreli bloklayan senkron kod parçacıkları veya yanlış eşzamanlı kaynak yönetimi nedeniyle ortaya çıkar. Örneğin, bir korutinin sonsuza dek bekleyen bir
awaitifadesi içerisinde takılı kalması veya kritik bir bölgeye erişim kontrolünün yanlış yapılması kilitlenmelere yol açabilir.asyncio'nun sağladığı senkronizasyon primitifleri (asyncio.Lock,asyncio.Semaphore) verun_in_executor()gibi yöntemlerle bu tür sorunların önüne geçilebilir. -
Hangi web framework'leri asenkron Python'ı destekler?
Birçok modern Python web framework'ü asenkron desteği sunar. En popüler olanlardan bazıları şunlardır:
- FastAPI: Modern, hızlı (Starlette üzerine kurulu) ve otomatik API dokümantasyonu sunan bir framework.
- Sanic: Flask benzeri bir sözdizimine sahip, yüksek performanslı asenkron web framework'ü.
- Starlette: FastAPI'nin temelini oluşturan hafif ve esnek bir ASGI (Asynchronous Server Gateway Interface) framework'ü.
- Django (3.0+): Geleneksel olarak senkron olan Django, 3.0 ve sonraki sürümlerinde asenkron view'lar, middleware'ler ve ORM sorguları için sınırlı da olsa asenkron destek eklemiştir.
Bu framework'ler, asenkron yapıyı kullanarak yüksek eşzamanlılık ve düşük gecikme süresi ile web servisleri geliştirmeyi kolaylaştırır.
