Python'da eşzamanlı programlamanın gücünü açığa çıkarın. Yüksek performanslı, ölçeklenebilir uygulamalar oluşturmak için Asyncio Görevlerini nasıl oluşturacağınızı, yöneteceğinizi ve iptal edeceğinizi öğrenin.
Python Asyncio'da Uzmanlaşmak: Görev Oluşturma ve Yönetimine Derinlemesine Bir Bakış
Modern yazılım geliştirme dünyasında performans çok önemlidir. Uygulamaların duyarlı olması, binlerce eşzamanlı ağ bağlantısını, veritabanı sorgusunu ve API çağrısını terlemeden işlemesi beklenir. G/Ç bağlı işlemler için—programın zamanının çoğunu bir ağ veya disk gibi harici kaynakları bekleyerek geçirdiği durumlarda—geleneksel senkron kod önemli bir darboğaz haline gelebilir. İşte asenkron programlamanın parladığı yer burasıdır ve Python'un asyncio
kitaplığı bu gücün kilidini açmanın anahtarıdır.
asyncio
'nun eşzamanlılık modelinin kalbinde basit ama güçlü bir kavram yatar: Görev. Korutinler ne yapılacağını tanımlarken, Görevler aslında işleri yapan şeydir. Bunlar, Python programlarınızın aynı anda birden çok işlemi yönetmesine olanak tanıyan, verimi ve yanıt verme hızını önemli ölçüde artıran eşzamanlı yürütmenin temel birimidir.
Bu kapsamlı kılavuz sizi asyncio.Task
'a derinlemesine bir yolculuğa çıkaracak. Temel oluşturma işlemlerinden gelişmiş yönetim kalıplarına, iptale ve en iyi uygulamalara kadar her şeyi keşfedeceğiz. İster yüksek trafikli bir web hizmeti, ister veri kazıma aracı veya gerçek zamanlı bir uygulama oluşturuyor olun, Görevlerde uzmanlaşmak, modern herhangi bir Python geliştiricisi için temel bir beceridir.
Korutin Nedir? Hızlı Bir Hatırlatma
Koşmadan önce yürümeliyiz. Ve asyncio
dünyasında, yürüyüş korutinleri anlamaktır. Korutin, async def
ile tanımlanan özel bir fonksiyon türüdür.
Normal bir Python fonksiyonunu çağırdığınızda, baştan sona kadar çalışır. Ancak, bir korutin fonksiyonunu çağırdığınızda, hemen çalışmaz. Bunun yerine, bir korutin nesnesi döndürür. Bu nesne, yapılacak işin bir planıdır, ancak kendi başına etkindir. Başlatılabilen, askıya alınabilen ve devam ettirilebilen duraklatılmış bir hesaplamadır.
import asyncio
async def say_hello(name: str):
print(f"Preparing to greet {name}...")
await asyncio.sleep(1) # Simulate a non-blocking I/O operation
print(f"Hello, {name}!")
# Calling the function doesn't run it, it creates a coroutine object
coro = say_hello("World")
print(f"Created a coroutine object: {coro}")
# To actually run it, you need to use an entry point like asyncio.run()
# asyncio.run(coro)
Sihirli anahtar kelime await
'tir. Etkinlik döngüsüne şunu söyler: "Bu işlem biraz zaman alabilir, bu yüzden beni burada duraklatmaktan ve başka bir şey üzerinde çalışmaktan çekinmeyin. Bu işlem tamamlandığında beni uyandırın." Duraklatma ve bağlamları değiştirme yeteneği, eşzamanlılığı sağlayan şeydir.
Eşzamanlılığın Kalbi: asyncio.Task'i Anlamak
Yani, bir korutin bir plandır. Mutfakta (etkinlik döngüsü) pişirmeye başlamasını nasıl söyleriz? İşte asyncio.Task
devreye giriyor.
Bir asyncio.Task
, bir korutini saran ve asyncio etkinlik döngüsünde yürütülmek üzere planlayan bir nesnedir. Bunu şöyle düşünün:
- Korutin (
async def
): Bir yemek için ayrıntılı bir tarif. - Etkinlik Döngüsü: Tüm pişirme işlemlerinin gerçekleştiği merkezi mutfak.
await my_coro()
: Mutfakta duruyor ve tarifi adım adım kendiniz takip ediyorsunuz. Yemek tamamlanana kadar başka bir şey yapamazsınız. Bu, sıralı yürütmedir.asyncio.create_task(my_coro())
: Tarifi mutfaktaki bir şefe (Görev) veriyor ve "Bunun üzerinde çalışmaya başla" diyorsunuz. Şef hemen başlar ve siz daha fazla tarif dağıtmak gibi başka şeyler yapmakta özgürsünüz. Bu, eşzamanlı yürütmedir.
Temel fark, asyncio.create_task()
'in korutini "arka planda" çalışacak şekilde planlaması ve hemen kodunuza kontrolü geri vermesidir. Devam eden bu işleme bir tanıtıcı görevi gören bir Task
nesnesi geri alırsınız. Bu tanıtıcıyı durumunu kontrol etmek, iptal etmek veya daha sonra sonucunu beklemek için kullanabilirsiniz.
İlk Görevlerinizi Oluşturma: `asyncio.create_task()` Fonksiyonu
Bir Görev oluşturmanın birincil yolu, asyncio.create_task()
fonksiyonudur. Bir korutin nesnesini argüman olarak alır ve yürütülmek üzere planlar.
Temel Sözdizimi
Kullanımı basittir:
import asyncio
async def my_background_work():
print("Starting background work...")
await asyncio.sleep(2)
print("Background work finished.")
return "Success"
async def main():
print("Main function started.")
# Schedule my_background_work to run concurrently
task = asyncio.create_task(my_background_work())
# While the task runs, we can do other things
print("Task created. Main function continues to run.")
await asyncio.sleep(1)
print("Main function did some other work.")
# Now, wait for the task to complete and get its result
result = await task
print(f"Task completed with result: {result}")
asyncio.run(main())
Çıktının, `ana` fonksiyonunun görevi oluşturduktan hemen sonra yürütülmesine nasıl devam ettiğini fark edin. Engellemiyor. Sadece sonunda açıkça `await task` olduğumuzda duraklıyor.
Pratik Bir Örnek: Eşzamanlı Web İstekleri
Yaygın bir senaryo ile Görevlerin gerçek gücünü görelim: birden çok URL'den veri getirme. Bunun için, `pip install aiohttp` ile yükleyebileceğiniz popüler `aiohttp` kitaplığını kullanacağız.
Öncelikle, sıralı (yavaş) yolu görelim:
import asyncio
import aiohttp
import time
async def fetch_status(session, url):
async with session.get(url) as response:
return response.status
async def main_sequential():
urls = [
"https://www.python.org",
"https://www.google.com",
"https://www.github.com",
"https://www.microsoft.com"
]
start_time = time.time()
async with aiohttp.ClientSession() as session:
for url in urls:
status = await fetch_status(session, url)
print(f"Status for {url}: {status}")
end_time = time.time()
print(f"Sequential execution took {end_time - start_time:.2f} seconds")
# To run this, you would use: asyncio.run(main_sequential())
Her istek yaklaşık 0,5 saniye sürerse, toplam süre kabaca 2 saniye olacaktır, çünkü her `await` bu tek istek bitene kadar döngüyü bloke eder.
Şimdi, Görevlerle eşzamanlılığın gücünü serbest bırakalım:
import asyncio
import aiohttp
import time
# fetch_status coroutine remains the same
async def fetch_status(session, url):
async with session.get(url) as response:
return response.status
async def main_concurrent():
urls = [
"https://www.python.org",
"https://www.google.com",
"https://www.github.com",
"https://www.microsoft.com"
]
start_time = time.time()
async with aiohttp.ClientSession() as session:
# Create a list of tasks, but don't await them yet
tasks = [asyncio.create_task(fetch_status(session, url)) for url in urls]
# Now, wait for all tasks to complete
statuses = await asyncio.gather(*tasks)
for url, status in zip(urls, statuses):
print(f"Status for {url}: {status}")
end_time = time.time()
print(f"Concurrent execution took {end_time - start_time:.2f} seconds")
asyncio.run(main_concurrent())
Eşzamanlı sürümü çalıştırdığınızda, dramatik bir fark göreceksiniz. Toplam süre kabaca tümünün toplamı değil, en uzun tek isteğin süresi olacaktır. Bunun nedeni, ilk `fetch_status` korutini `await session.get(url)`'ye ulaştığı anda, etkinlik döngüsünün onu duraklatması ve hemen bir sonrakine başlamasıdır. Tüm ağ istekleri etkili bir şekilde aynı anda gerçekleşir.
Bir Görev Grubunu Yönetme: Temel Desenler
Bireysel görevler oluşturmak harikadır, ancak gerçek dünya uygulamalarında genellikle bunların tüm grubunu başlatmanız, yönetmeniz ve senkronize etmeniz gerekir. `asyncio` bunun için çeşitli güçlü araçlar sağlar.
Modern Yaklaşım (Python 3.11+): `asyncio.TaskGroup`
Python 3.11'de tanıtılan `TaskGroup`, ilgili görevlerden oluşan bir grubu yönetmenin yeni, önerilen ve en güvenli yoludur. Yapılandırılmış eşzamanlılık olarak bilinen şeyi sağlar.
`TaskGroup`'un temel özellikleri:
- Garantili Temizleme:
async with
bloğu, içinde oluşturulan tüm görevler tamamlanana kadar çıkmayacaktır. - Sağlam Hata İşleme: Grup içindeki herhangi bir görev bir istisna oluşturursa, gruptaki diğer tüm görevler otomatik olarak iptal edilir ve istisna (veya bir `ExceptionGroup`)
async with
bloğundan çıkarken yeniden oluşturulur. Bu, yetim görevleri önler ve öngörülebilir bir durum sağlar.
İşte nasıl kullanılacağı:
import asyncio
async def worker(delay):
print(f"Worker starting, will sleep for {delay}s")
await asyncio.sleep(delay)
# This worker will fail
if delay == 2:
raise ValueError("Something went wrong in worker 2")
print(f"Worker with delay {delay} finished")
return f"Result from {delay}s"
async def main():
print("Starting main with TaskGroup...")
try:
async with asyncio.TaskGroup() as tg:
task1 = tg.create_task(worker(1))
task2 = tg.create_task(worker(2)) # This one will fail
task3 = tg.create_task(worker(3))
print("Tasks created in the group.")
# This part of the code will NOT be reached if an exception occurs
# The results would be accessed via task1.result(), etc.
print("All tasks completed successfully.")
except* ValueError as eg: # Note the `except*` for ExceptionGroup
print(f"Caught an exception group with {len(eg.exceptions)} exceptions.")
for exc in eg.exceptions:
print(f" - {exc}")
print("Main function finished.")
asyncio.run(main())
Bunu çalıştırdığınızda, `worker(2)`'nin bir hata oluşturduğunu göreceksiniz. `TaskGroup` bunu yakalar, çalışan diğer görevleri (örneğin `worker(3)`) iptal eder ve ardından `ValueError` içeren bir `ExceptionGroup` oluşturur. Bu desen, güvenilir sistemler oluşturmak için inanılmaz derecede sağlamdır.
Klasik Çalışma Atı: `asyncio.gather()`
`TaskGroup`'tan önce, `asyncio.gather()`, birden çok bekletilebilir öğeyi eşzamanlı olarak çalıştırmanın ve hepsinin bitmesini beklemenin en yaygın yoluydu.
gather()
, bir korutinler veya Görevler dizisi alır, hepsini çalıştırır ve sonuçlarının bir listesini girdilerle aynı sırada döndürür. "Bunların hepsini çalıştır ve bana tüm sonuçları ver" gibi yaygın durum için yüksek seviyeli, kullanışlı bir fonksiyondur.
import asyncio
async def fetch_data(source, delay):
print(f"Fetching from {source}...")
await asyncio.sleep(delay)
return {"source": source, "data": f"some data from {source}"}
async def main():
# gather can take coroutines directly
results = await asyncio.gather(
fetch_data("API", 2),
fetch_data("Database", 3),
fetch_data("Cache", 1)
)
print(results)
asyncio.run(main())
`gather()` ile Hata İşleme: Varsayılan olarak, `gather()`'e iletilen bekletilebilir öğelerden herhangi biri bir istisna oluşturursa, `gather()` bu istisnayı hemen yayar ve çalışan diğer görevler iptal edilir. Bu davranışı `return_exceptions=True` ile değiştirebilirsiniz. Bu modda, bir istisna oluşturmak yerine, ilgili konuma sonuç listesine yerleştirilecektir.
# ... inside main()
results = await asyncio.gather(
fetch_data("API", 2),
asyncio.create_task(worker(1)), # This will raise a ValueError
fetch_data("Cache", 1),
return_exceptions=True
)
# results will contain a mix of successful results and exception objects
print(results)
Hassas Kontrol: `asyncio.wait()`
asyncio.wait()
, bir görev grubu üzerinde daha ayrıntılı kontrol sağlayan daha düşük seviyeli bir fonksiyondur. `gather()`'den farklı olarak, doğrudan sonuç döndürmez. Bunun yerine, iki görev kümesi döndürür: `done` ve `pending`.
En güçlü özelliği, şu olabilen `return_when` parametresidir:
asyncio.ALL_COMPLETED
(varsayılan): Tüm görevler bittiğinde döner.asyncio.FIRST_COMPLETED
: En az bir görev biter bitmez döner.asyncio.FIRST_EXCEPTION
: Bir görev bir istisna oluşturduğunda döner. Hiçbir görev bir istisna oluşturmazsa, `ALL_COMPLETED`'e eşdeğerdir.
Bu, birden çok yedekli veri kaynağını sorgulamak ve yanıt veren ilk kaynağı kullanmak gibi senaryolar için son derece kullanışlıdır:
import asyncio
async def query_source(name, delay):
await asyncio.sleep(delay)
return f"Result from {name}"
async def main():
tasks = [
asyncio.create_task(query_source("Fast Mirror", 0.5)),
asyncio.create_task(query_source("Slow Main DB", 2.0)),
asyncio.create_task(query_source("Geographic Replica", 0.8))
]
done, pending = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
# Get the result from the completed task
first_result = done.pop().result()
print(f"Got first result: {first_result}")
# We now have pending tasks that are still running. It's crucial to clean them up!
print(f"Cancelling {len(pending)} pending tasks...")
for task in pending:
task.cancel()
# Await the cancelled tasks to allow them to process the cancellation
await asyncio.gather(*pending, return_exceptions=True)
print("Cleanup complete.")
asyncio.run(main())
TaskGroup - gather() - wait(): Ne Zaman Hangisini Kullanmalı?
- Varsayılan seçiminiz olarak `asyncio.TaskGroup` (Python 3.11+) kullanın. Yapılandırılmış eşzamanlılık modeli daha güvenli, daha temiz ve tek bir mantıksal işleme ait bir görev grubunu yönetmek için daha az hataya eğilimlidir.
- Bağımsız bir görev grubunu çalıştırmanız ve yalnızca sonuçlarının bir listesini istemeniz gerektiğinde `asyncio.gather()` kullanın. Hala çok kullanışlıdır ve özellikle 3.11'den önceki Python sürümlerinde basit durumlar için biraz daha kısadır.
- Tamamlama koşulları üzerinde hassas kontrole ihtiyacınız olduğunda (örneğin, ilk sonucu beklemek) ve kalan bekleyen görevleri manuel olarak yönetmeye hazır olduğunuzda `asyncio.wait()` kullanın.
Görev Yaşam Döngüsü ve Yönetimi
Bir Görev oluşturulduktan sonra, `Task` nesnesindeki yöntemleri kullanarak onunla etkileşim kurabilirsiniz.
Görev Durumunu Kontrol Etme
task.done()
: Görev tamamlandıysa `True` döndürür (başarıyla, bir istisna ile veya iptal yoluyla).task.cancelled()
: Görev iptal edildiyse `True` döndürür.task.exception()
: Görev bir istisna oluşturduysa, bu istisna nesnesini döndürür. Aksi takdirde, `None` döndürür. Bunu yalnızca görev `done()` olduktan sonra çağırabilirsiniz.
Sonuçları Alma
Bir görevin sonucunu almanın ana yolu, basitçe `await task` kullanmaktır. Görev başarıyla tamamlandıysa, bu değeri döndürür. Bir istisna oluşturduysa, `await task` bu istisnayı yeniden oluşturacaktır. İptal edildiyse, `await task` bir `CancelledError` oluşturacaktır.
Alternatif olarak, bir görevin `done()` olduğunu biliyorsanız, `task.result()`'ı çağırabilirsiniz. Bu, değerleri döndürme veya istisnalar oluşturma açısından `await task` ile aynı şekilde davranır.
İptalin Sanatı
Uzun süren işlemleri düzgün bir şekilde iptal edebilmek, sağlam uygulamalar oluşturmak için kritik öneme sahiptir. Bir zaman aşımı, bir kullanıcı isteği veya sistemdeki başka bir hata nedeniyle bir görevi iptal etmeniz gerekebilir.
Bir görevi, task.cancel()
yöntemini çağırarak iptal edersiniz. Ancak bu, görevi hemen durdurmaz. Bunun yerine, bir sonraki await
noktasında korutin içinde bir `CancelledError` istisnası oluşturulmasını planlar. Bu çok önemli bir ayrıntıdır. Korutine çıkmadan önce temizleme şansı verir.
İyi davranan bir korutin, tipik olarak dosya tutamaçları veya veritabanı bağlantıları gibi kaynakların kapatılmasını sağlamak için bir `try...finally` bloğu kullanarak bu `CancelledError`'ı düzgün bir şekilde ele almalıdır.
import asyncio
async def resource_intensive_task():
print("Acquiring resource (e.g., opening a connection)...")
try:
for i in range(10):
print(f"Working... step {i+1}")
await asyncio.sleep(1) # This is an await point where CancelledError can be injected
except asyncio.CancelledError:
print("Task was cancelled! Cleaning up...")
raise # It's good practice to re-raise CancelledError
finally:
print("Releasing resource (e.g., closing connection). This always runs.")
async def main():
task = asyncio.create_task(resource_intensive_task())
# Let it run for a bit
await asyncio.sleep(2.5)
print("Main decides to cancel the task.")
task.cancel()
try:
await task
except asyncio.CancelledError:
print("Main has confirmed the task was cancelled.")
asyncio.run(main())
`finally` bloğunun yürütülmesi garanti edilir, bu da onu temizleme mantığı için mükemmel bir yer yapar.
`asyncio.timeout()` ve `asyncio.wait_for()` ile Zaman Aşımları Ekleme
Manuel olarak uyumak ve iptal etmek sıkıcıdır. `asyncio` bu ortak desen için yardımcılar sağlar.
Python 3.11+'da, `asyncio.timeout()` bağlam yöneticisi tercih edilen yoldur:
async def long_running_operation():
await asyncio.sleep(10)
print("Operation finished")
async def main():
try:
async with asyncio.timeout(2): # Set a 2-second timeout
await long_running_operation()
except TimeoutError:
print("The operation timed out!")
asyncio.run(main())
Daha eski Python sürümleri için `asyncio.wait_for()`'ı kullanabilirsiniz. Benzer şekilde çalışır, ancak bekletilebilir öğeyi bir fonksiyon çağrısında sarar:
async def main_legacy():
try:
await asyncio.wait_for(long_running_operation(), timeout=2)
except asyncio.TimeoutError:
print("The operation timed out!")
asyncio.run(main_legacy())
Her iki araç da zaman aşımına ulaşıldığında iç görevi iptal ederek ve bir `TimeoutError` (ki bu `CancelledError`'ın bir alt sınıfıdır) oluşturarak çalışır.
Yaygın Tuzaklar ve En İyi Uygulamalar
Görevlerle çalışmak güçlüdür, ancak kaçınılması gereken birkaç yaygın tuzak vardır.
- Tuzak: "Ateşle ve Unut" Hatası. `create_task` ile bir görev oluşturmak ve ardından hiçbir zaman beklememek (veya `TaskGroup` gibi bir yönetici) tehlikelidir. Bu görev bir istisna oluşturursa, istisna sessizce kaybolabilir ve programınız görev işini tamamlamadan önce bile çıkabilir. Sonucunu beklemekten sorumlu olan her görev için her zaman net bir sahibiniz olsun.
- Tuzak: `asyncio.run()`'ı `create_task()` ile karıştırmak. `asyncio.run(my_coro())`, bir `asyncio` programını başlatmak için ana giriş noktasıdır. Yeni bir etkinlik döngüsü oluşturur ve verilen korutini tamamlanana kadar çalıştırır. `asyncio.create_task(my_coro())` eşzamanlı yürütmeyi planlamak için zaten çalışan bir async fonksiyonun içinde kullanılır.
- En İyi Uygulama: Modern Python için `TaskGroup` kullanın. Tasarımı, unutulan görevler ve ele alınmamış istisnalar gibi birçok yaygın hatayı önler. Python 3.11 veya sonraki bir sürümdeyseniz, onu varsayılan seçiminiz yapın.
- En İyi Uygulama: Görevlerinizi Adlandırın. Bir görev oluştururken `name` parametresini kullanın: `asyncio.create_task(my_coro(), name='DataProcessor-123')`. Bu, hata ayıklama için paha biçilmezdir. Çalışan tüm görevleri listelediğinizde, anlamlı adlara sahip olmak programınızın ne yaptığını anlamanıza yardımcı olur.
- En İyi Uygulama: Düzgün Kapanmayı Sağlayın. Uygulamanızın kapatılması gerektiğinde, çalışan tüm arka plan görevlerini iptal etmek ve düzgün bir şekilde temizlenmelerini beklemek için bir mekanizmaya sahip olduğunuzdan emin olun.
Gelişmiş Kavramlar: Ötesine Bir Bakış
Hata ayıklama ve içe dönüklük için `asyncio` birkaç kullanışlı fonksiyon sağlar:
asyncio.current_task()
: Şu anda yürütülen kod için `Task` nesnesini döndürür.asyncio.all_tasks()
: Etkinlik döngüsü tarafından şu anda yönetilen tüm `Task` nesnelerinin bir kümesini döndürür. Bu, neyin çalıştığını görmek için hata ayıklama için harikadır.
`task.add_done_callback()`'i kullanarak görevlere tamamlama geri aramaları da ekleyebilirsiniz. Bu yararlı olsa da, genellikle daha karmaşık, geri arama stili bir kod yapısına yol açar. `await`, `TaskGroup` veya `gather` kullanan modern yaklaşımlar, okunabilirlik ve sürdürülebilirlik için genellikle tercih edilir.
Sonuç
`asyncio.Task`, modern Python'da eşzamanlılığın motorudur. Görevlerin nasıl oluşturulacağını, yönetileceğini ve yaşam döngüsünü düzgün bir şekilde nasıl ele alacağınızı anlayarak, G/Ç bağlı uygulamalarınızı yavaş, sıralı işlemlerden son derece verimli, ölçeklenebilir ve duyarlı sistemlere dönüştürebilirsiniz.
create_task()
ile bir korutin planlamanın temel kavramından, `TaskGroup`, `gather()` ve `wait()` ile karmaşık iş akışlarını düzenlemeye kadar yolculuğu ele aldık. Ayrıca, dayanıklı yazılım oluşturmak için sağlam hata işlemenin, iptalin ve zaman aşımlarının kritik önemini de inceledik.
Asenkron programlama dünyası geniştir, ancak Görevlerde uzmanlaşmak atabileceğiniz en önemli adımdır. Denemeye başlayın. Uygulamanızın sıralı, G/Ç bağlı bir bölümünü eşzamanlı görevleri kullanacak şekilde dönüştürün ve performans kazanımlarına kendiniz tanık olun. Eşzamanlılığın gücünü kucaklayın ve yeni nesil yüksek performanslı Python uygulamalarını oluşturmak için iyi bir donanıma sahip olacaksınız.