Yerel asenkron hesaplamaları gönderim sırasıyla tamamlaması gereken bir Mac uygulaması için, her görevi öncekinin arkasına zincirleyen eşzamanlı bir ana aktör gönderim yöntemi kullanın. Tek başına aktör yalıtımı bu sıralama garantisini sağlamaz.
Bu anlatım, sentetik tamsayı hesaplamaları için küçük bir kuyruk oluşturur. Gönderimin tam olarak ne zaman gerçekleştiğini tanımlar, askıya alma noktalarında sırayı korur, her hesaplamanın sonucunu döndürür ve önceki bir iş başarısız olsa bile sonraki işlerin devam etmesini sağlar.
Katı gönderim sırası ne demektir?
Bir masaüstü hesap makinesinin A, B ve C işlerini gönderdiğini varsayalım. Gerekli sözleşme şudur: A’nın hesaplaması, B’nin hesaplaması başlamadan önce tamamlanır; B de C başlamadan önce tamamlanır. Bağımsız olarak başlatılmış üç görevin sonuçlarını alfabetik sırayla beklemek, yalnızca sonuçların gözlemlenme sırasını düzenler.
Gönderim sınırı önemlidir. Bu uygulamada gönderim, ana aktör yalıtımlı submit yöntemi çalışıp zincirdeki bir sonraki bağlantıyı kurduğunda gerçekleşir. Aynı ana aktör bağlamından yapılan ardışık eşzamanlı çağrılar, aralarında askıya alma olmadan A, B, C sırasını oluşturur.
Bağımsız görevler eşzamanlı olarak gönderim isterse, geliş sıraları belirtilmemiştir. Kuyruk, gönderim yönteminin onları kabul ettiği sırayı korur. Uygulamanız bu üreticiler arasında belirli bir sıra gerektiriyorsa, gönderimden önce bu sırayı belirleyin; örneğin sonlu bir grubu açıkça sıralanmış bir dizi içinde toplayın.
Tasarım, kısa muhasebe adımı için ana aktörü kullanır. Apple, MainActor’ı yürütücüsü ana dispatch kuyruğuna eşdeğer olan tekil bir aktör olarak tanımlar. Aşağıdaki asıl hesaplama ayrı bir işçi aktöre aittir. MainActor belgeleri
Bir aktör ya da seri işlem kuyruğu neden yeterli değil?
Bir aktör, aktör yalıtımlı işleri her seferinde bir parça çalıştırır; ancak asenkron bir yöntem askıya alınabilir ve başka bir çağrının ilerlemesine izin verebilir. Swift’in aktör tasarımı ayrıca bir aktörü bekleyen görevlerin, başlangıçta beklemeye başladıkları sırada çalışacağının garanti edilmediğini açıkça belirtir. Dolayısıyla tüm asenkron hesaplamayı bir aktörün içine koymak, tek başına önceki iş bitmeden sonrakinin başlamaması davranışını sağlamaz. SE-0306: Aktörler
Ayrı Task örnekleri oluşturmak da aralarında bağımlılık kurmaz. Görevler oluşturulduktan sonra çalışmaya başlayabilir; tanıtıcılarını saklamak onları beklemenizi ya da iptal etmenizi sağlar, ancak bir yürütme sırası oluşturmaz. Task belgeleri
Foundation farklı bir sıralama mekanizması sunar. OperationQueue, hazır olma durumuna, önceliğe ve bağımlılıklara göre zamanlama yapar; Apple, belirli bir yürütme sırası önemli olduğunda bağımlılıkların kullanılmasını önerir. maxConcurrentOperationCount değerini 1 olarak ayarlamak eşzamanlılığı sınırlar, ancak sıralama gereksinimi yine de açık bağımlılıklar gerektirir. OperationQueue belgeleri
Uygulamanızın mevcut gereksinimlerine göre mekanizmayı seçin:
| Durum | Uygun yaklaşım | Sırayı ne sağlar? |
|---|---|---|
| Tek bir asenkron işlevde bilinen bir grup iş | Devam etmeden önce her hesaplamayı bekleyen bir döngü | Sıralı kontrol akışı |
| Zaman içinde masaüstü eylemlerinden gelen işler | Görev zinciri kullanan eşzamanlı bir gönderim yöntemi | Her hesaplama, öncekinin tamamlanma kapısını bekler |
| Mevcut Foundation işlemleri | Kuyruğa eklemeden önce yapılandırılan bağımlılıklar | Her işlem, önceki işlemin bitiş durumuna bağlıdır |
Görev zinciri burada kullanışlıdır; çünkü hesaplamalar daha sonra çalışırken gönderim hemen bir tanıtıcı döndürür. Gelen iş bulunmayan sabit bir grup için sıralı döngü genellikle daha az kod gerektirir.
Asenkron hesaplamaların tamamını bekleyen bir kuyruk oluşturun
Kuyruk, tail adlı özel ve hata fırlatmayan bir tamamlanma görevi saklar. Her gönderim mevcut kuyruğun sonunu yakalar, onu bekleyen bir hesaplama görevi oluşturur ve hesaplamanın tamamlandığını gözlemleyen yeni bir görevle kuyruğun sonunu değiştirir.
Tamamlanma görevi, bir görev tamamlandıktan sonra başarıyı ya da hatayı temsil eden result değerini bekler. Böylece başarısız bir hesaplama, hesaplamanın kendi tanıtıcısındaki hatayı kaybetmeden sonraki işin devam etmesini sağlar. Çağıranlar bu hatayı yine de tanıtıcı üzerinden alabilir. Task sonuç belgeleri
Aşağıdaki örnek, Swift 6 araç zincirine sahip bir Mac için eksiksiz bir komut satırı demosudur. OrderedDemo.swift adıyla kaydedin. Kaynak incelendi, ancak bu araştırma ortamında Swift derleyicisi bulunmadığından derlenemedi.
import Foundation
@MainActor
final class OrderedIntQueue {
private var tail: Task<Void, Never>?
func submit(
_ operation: @escaping @Sendable () async throws -> Int
) -> Task<Int, Error> {
let predecessor = tail
let job = Task<Int, Error> {
if let predecessor {
await predecessor.value
}
try Task.checkCancellation()
return try await operation()
}
tail = Task<Void, Never> {
_ = await job.result
}
return job
}
}
enum CalculationError: Error {
case inputOutOfRange
}
actor CalculationWorker {
private var events: [String] = []
func square(_ input: Int, label: String) async throws -> Int {
events.append("start \(label)")
guard (-1000...1000).contains(input) else {
throw CalculationError.inputOutOfRange
}
await Task.yield()
try Task.checkCancellation()
let answer = input * input
events.append("finish \(label)")
return answer
}
func trace() -> [String] {
events
}
}
@main
struct OrderedDemo {
@MainActor
static func main() async {
let queue = OrderedIntQueue()
let worker = CalculationWorker()
let a = queue.submit {
try await worker.square(3, label: "A")
}
let b = queue.submit {
try await worker.square(4, label: "B")
}
let c = queue.submit {
try await worker.square(5, label: "C")
}
var answers: [Int] = []
for job in [a, b, c] {
switch await job.result {
case .success(let answer):
answers.append(answer)
case .failure(let error):
print("Calculation failed: \(error)")
}
}
let events = await worker.trace()
precondition(answers == [9, 16, 25])
precondition(events == [
"start A", "finish A",
"start B", "finish B",
"start C", "finish C"
])
print(events.joined(separator: "\n"))
print("Answers: \(answers)")
}
}
Kaydedilen programı bulunduğu dizinde Terminal’den derleyip çalıştırın:
swiftc -swift-version 6 -parse-as-library OrderedDemo.swift -o OrderedDemo
./OrderedDemo
Beklenen çıktı, örneğin girdilerinden ve bağımlılık zincirinden çıkar:
start A
finish A
start B
finish B
start C
finish C
Answers: [9, 16, 25]
Onaylamalar hem döndürülen değerleri hem de hesaplama sınırlarını denetler. Tanıtıcıları gönderim sırasıyla beklerken yalnızca sonuçları yazdırmak, hesaplamaların bu sırada çalıştığını kanıtlamaz.
İşçi, hesaplamanın askıya alınma fırsatı içermesi için bilerek Task.yield() çağırır. Yield işlemi başka görevlerin çalışmasına izin verir, ancak yürütücü aynı görevi hemen yeniden başlatabilir. Bu nedenle kuyruğun sıralaması, yield işleminin görev değiştireceği varsayımından değil, bağımlılıklardan kaynaklanır. Task yield belgeleri
Zincir, askıya alma sırasında sırayı nasıl koruyor?
Üç ayrıntı bu garantiyi sağlar. İlk olarak, submit içinde askıya alma noktası yoktur; bu nedenle başka bir ana aktör gönderimi, tail değerini yakalayıp değiştirdiği sırada araya giremez. Swift, askıya alma noktaları içermeyen aktör yalıtımlı kodun, bu sırayı başka bir aktör görevi kesintiye uğratmadan çalıştığını belgeler. Eşzamanlılık: Aktörler
İkinci olarak, her iş hesaplama closure’ını çağırmadan önce yakaladığı öncülünü bekler. Hata fırlatmayan bir görevin value değerine erişmek, o görevin tamamlanmasını bekler. Sonraki işin bekleme amaçlı muhasebe adımı erkenden çalışabilir, ancak hesaplaması erkenden başlayamaz. Hata fırlatmayan Task value belgeleri
Üçüncü olarak, öncül kapısı yalnızca önceki iş sonucunu ya da hatasını ürettikten sonra tamamlanır. Bağlantıları geriye doğru izlemek, kabul edilen her gönderim için sırayı kurar: C, B’nin tamamlanma kapısını bekler; bu kapı B’yi bekler; B de A’nın tamamlanma kapısını bekler.
Bu sıralama garantisi, gönderilen her closure içinde beklenen işler için geçerlidir. Bir closure başka bir yapılandırılmamış görev başlatıp onu beklemeden dönerse, bu ek iş closure’dan daha uzun süre devam edebilir. Gerekli tüm hesaplama adımlarını dönmeden önce beklenen işleme dahil edin.
Masaüstü entegrasyonu için ilgili eylemleri yöneten ana aktör denetleyicisinde ya da modelinde tek bir kuyruk örneği tutun. submit yöntemini doğrudan bu bağlamdan çağırın, ardından tamamlanmayı gözlemlemek için döndürülen tanıtıcıyı kullanın. Önce bağımsız bir görev oluşturup başladıktan sonra gönderim yapmak, gönderim sınırını o görevin daha sonra çalışacağı ana taşır.
Hatalar ve iptal nasıl ele alınmalı?
Bu uygulama, hatadan sonra devam etme ilkesini izler. Başarısız bir iş kendi hatasını korur, tamamlanma kapısı kapanır ve kabul edilen sonraki iş hesaplama yapmaya uygun hale gelir. Demo, ilk hatadan sonra kalan sonuçları bırakmak yerine her tanıtıcının result değerini inceler.
Basit bir hata denetimi için B’nin girdisini 4 yerine 1001 yapın. İşçi, çarpma işleminden önce bu girdiyi reddeder. Beklenen yanıtları [9, 25] olarak, beklenen izi de start A, finish A, start B, start C, finish C olarak güncelleyin. Program hatayı yazdırmalı ve yine de C’yi hesaplamalıdır. Girdi sınırı, bu aritmetik işlemleri küçük tutmak için seçilmiş örnek amaçlı bir sözleşmedir.
İptal işbirliğine dayalıdır. Kuyruk, öncülünü bekledikten sonra iptali denetler; işçi de yield işleminden sonra tekrar denetler. İptal istendiğinde Task.checkCancellation(), CancellationError fırlatır. Task checkCancellation belgeleri
Yürütme öncesinde iptali denetlemek için C’yi gönderdikten hemen sonra ve sonuç döngüsünden önce b.cancel() çağrısını yapın. Kesintisiz ana aktör gönderimlerinde B henüz hesaplamasına başlamamış olur. Beklenen yanıtları [9, 25] olarak güncelleyin ve B’ye ait iki girdiyi beklenen izden çıkarın. B, iptal bildirmeden önce A’nın tamamlanma kapısını beklemeye devam eder; sonra C ilerler.
Tamamlanma kapılarını özel tutun. Bunlar zincirin muhasebesini temsil eder; döndürülen tanıtıcılar ise tek tek hesaplamaları temsil eder. Bir işi iptal etmek, önceki hesaplamanın durması ya da sonraki hesaplamaların hâlâ çalışmakta olan bir hesaplamadan önce başlaması gerektiği anlamına gelmez.
Uygulamanız bunun yerine bir hata sonrası tüm grubu durdurmayı gerektiriyorsa, açık bir grup ilkesi gerekir. Kapının hataları iletmesini sağlamak, hangi sonraki işlerin yürütüleceğini ve sonuçlarının nasıl raporlanacağını değiştirir.
Bunun yerine Foundation bağımlılıklarını ne zaman kullanmalı?
Masaüstü uygulaması işleri zaten Foundation Operation nesneleri olarak temsil ediyorsa, kuyruğa eklemeden önce her işlemle önceki işlemi arasında bağımlılık kurun. Foundation, önceki işlem başarılı ya da başarısız olsun, tamamlandığında bağımlılığı karşılanmış sayar; başarıya bağlı davranış ayrı sonuç işleme gerektirir. Operation belgeleri
Swift asenkron işlevlerini bu modele bağlarken dikkatli olun. Bir Task başlatıp hemen dönen eşzamanlı bir işlem, görevin tüm yaşam süresini temsil etmez. Özel bir asenkron işlem, yürütme ve bitiş durumunu; gerekli bildirimler de dahil olmak üzere, doğru biçimde yönetmelidir ki bağımlı işlemler amaçlanan işin tamamlanmasını beklesin. Apple bu sorumlulukları asenkron işlemler ve bitiş durumu raporlaması başlıkları altında belgeler. Operation yaşam döngüsü belgeleri
Burada geliştirilen yerel asenkron hesap makinesi için görev zinciri sözleşmeyi görünür kılar: gönderimleri eşzamanlı kabul edin, öncekinin tamamlanmasını bekleyin, hesaplamanın tamamını çalıştırın ve sonucunu sunun. Kuyruğu masaüstü eylemlerine bağlarken bu sınırları koruyun ve yürütmeyi döndürülen değerlerin yanı sıra başlangıç ve bitiş izleriyle de doğrulayın.
Kaynaklar
- MainActor — Apple
- SE-0306: Aktörler — Swift
- Task — Apple
- OperationQueue — Apple
- result — Apple
- yield() — Apple
- Eşzamanlılık — Swift
- value: Hata fırlatmayan görev — Apple
- checkCancellation() — Apple
- Operation — Apple