Uzun süre yaşayan tek bir Foundation OperationQueue kullanın ve hesaplamaları göndermeden önce maxConcurrentOperationCount değerini ayarlayın. Her düğme basışında bir istek alan bir Mac yardımcı uygulaması için bu, sonraki isteklerin beklemesine izin verirken eşzamanlı işleri sınırlar.
Bu eğitim, söz konusu masaüstü görevi için hesaplama motorunu ve davranışını kontrol edecek bağımsız bir programı oluşturur. Her istek sentetik tam sayılar içerir ve her sonuç bellekte kalır. Hesaplama, işlemin içinde eşzamanlı olarak yürütülür; böylece işlemin ömrü gerçek çalışmayı kapsar.
Tek tek gelen istekler için hangi eşzamanlılık aracı uygundur?
Bir OperationQueue, işlemleri tek tek kabul eder ve blok tabanlı addOperation(_:) yöntemi, gönderilen her closure’ı bir işleme sarar. Kuyruğu istekler boyunca yaşayan bir nesnede tutun, ardından gelen her öğe için bir closure gönderin. Apple’ın addOperation(_:) belgeleri bu gönderim yöntemini açıklar.
maxConcurrentOperationCount değerini 2 gibi pozitif bir sınıra ayarlayın. Apple bu özelliği, söz konusu kuyruğun aynı anda yürütülebilecek işlemlerinin azami sayısı olarak tanımlar. Sınır yalnızca ilgili kuyruğa aittir; her istek için ayrı bir kuyruk oluşturmak ortak sınırı etkisiz kılar. Sınırı sonradan düşürmek de yürütülmekte olan işlemleri durdurmaz. Apple’ın eşzamanlılık sınırı belgeleri bu sınırları açıklar.
Bir Swift görev grubu farklı bir ömre sahiptir. withTaskGroup kapsamı içinde yer alır, onu oluşturan görevin dışından kullanılamaz ve dönmeden önce alt görevlerinin tamamlanmasını bekler. Bu nedenle, görev grubunu gelecekteki ilgisiz düğme basışları için özellik olarak saklamak desteklenmez. Bu eşzamanlı hesaplama motoru için, saklanan bir işlem kuyruğu gereken ömrü ve açık eşzamanlılık ayarını sağlar. Swift standart kütüphanesinin görev grubu belgeleri bu kısıtlamaları açıklar.
| İş düzeniniz | Uygun yaklaşım |
|---|---|
| Bağımsız eşzamanlı hesaplamalar tek tek geliyor | Tek bir OperationQueue saklayın ve her hesaplama için bir işlem gönderin. |
| Ebeveyn asenkron işlem, sonlu sayıda alt göreve sahip | Bu ebeveynin kapsamı içinde görev grubu kullanın; sayısal bir sınır gerekiyorsa görev kabulünü açıkça denetleyin. |
| İşin masaüstü arayüzünü güncellemesi gerekiyor | Hesaplamayı ayrı yürütün, ardından sonucu MainActor üzerinde yalıtılmış koda iletin. |
Son satır, sonucun iletilmesiyle ilgilidir. Swift’in dil kılavuzu, ana aktörü arayüz durumu için kullanılacak yer olarak tanımlar ve açıkça ana aktöre yalıtılmış closure örnekleri gösterir. Pahalı aritmetiği bu iletim adımının dışında tutun. The Swift Programming Language: Concurrency, ana aktör yalıtımını açıklar.
Hesaplamayı ve tamamlanma sözleşmesini tanımlayın
Örnek, 1 ile 1_000_000 arasında bir tam sayı tanımlayıcısı ve yineleme sayısı kabul eder. Yineleme sayısı n olduğunda, 1 ile n arasındaki tüm tam sayıların karelerini toplar. Tanımlayıcıyla birlikte bir UInt64 sonucu döndürür. Tanımlayıcı çıktıyı etiketler; aritmetiği etkilemez.
Bunlar bu program için seçilmiş örnek sınırlardır. Kabul edilen en yüksek girdi için sonuç 333333833333500000 olur ve UInt64 içine sığar. Doğrulama gönderimden önce yapılır; dolayısıyla geçersiz bir istek hemen hata verir ve kuyruğa işlem eklenmez. Girdi kısıtlaması, döngü aralığının da geçerli kalmasını sağlar.
Her kabul edilen istek, işlemi tamamlandığında saklanan bir sonuç üretir. Sonuçlar bir dizide birikir; bu nedenle yinelenen tanımlayıcılar önceki sonuçların üzerine yazmaz. Örnek, okunabilir çıktı için artan ve birbirinden farklı tanımlayıcılar kullanır ve tanımlayıcıya göre sıralama yapar; bu görüntüleme sırası, yürütme sırasıyla ilgili bir iddia taşımaz.
Bu sözleşme, kuyruğun kullanılabilir kaldığını ve işlemlerinin tamamlandığını varsayar. Örnek iptali dışarıya açmaz. Sonradan iptal eklemek, tamamlanmamış istekler için ayrı bir sonuç durumu gerektirir; çünkü Apple, iptali durdurma isteği olarak tanımlar ve yürütülen işin bu isteğe yanıt vermesi gerektiğini belirtir. Apple’ın OperationQueue belgeleri, işlem ömrünü ve iptali açıklar.
Paylaşılan bir sınırla eksiksiz bir program oluşturun
Swift 6.2 veya daha yeni bir derleyiciye ve Xcode ya da komut satırı araçları üzerinden kullanılabilen macOS SDK’sına sahip bir Mac kullanın. Aşağıdaki eksiksiz programı main.swift olarak kaydedin. Aşağıdaki komutlar, hesaplama yardımcılarının bir uygulama hedefinin yalıtım ayarlarından bağımsız kalması için varsayılan yalıtımı açıkça nonisolated olarak seçer. Swift bu derleyici ayarını SE-0466: Control default actor isolation inference belgesinde açıklar.
Sonuç günlüğü, hem dizisini hem de etkinlik sayaçlarını korumak için NSLock kullanır. @unchecked Sendable uyumluluğu, bu eşzamanlamanın sorumluluğunu uygulamaya bırakır: değiştirilebilir duruma her erişim aynı kilidi alır. Apple bu sorumluluğu Sendable belgelerinde, kritik bölge korumasını ise NSLock belgelerinde açıklar.
import Foundation
enum InputError: Error {
case invalidIterations
}
struct Calculation: Sendable {
let id: Int
let iterations: Int
}
func sumOfSquares(_ job: Calculation) -> UInt64 {
var sum: UInt64 = 0
for integer in 1...job.iterations {
let value = UInt64(integer)
sum += value * value
}
return sum
}
final class ExecutionLog: @unchecked Sendable {
private let lock = NSLock()
private var active = 0
private var peak = 0
private var rows: [(id: Int, value: UInt64)] = []
func begin() {
lock.lock()
defer { lock.unlock() }
active += 1
peak = max(peak, active)
}
func finish(id: Int, value: UInt64) {
lock.lock()
defer { lock.unlock() }
rows.append((id: id, value: value))
active -= 1
}
func snapshot() -> (
rows: [(id: Int, value: UInt64)], peak: Int
) {
lock.lock()
defer { lock.unlock() }
return (rows, peak)
}
}
final class CalculationPool {
private let queue = OperationQueue()
private let log = ExecutionLog()
init(limit: Int) {
precondition(limit > 0)
queue.name = "Synthetic calculations"
queue.maxConcurrentOperationCount = limit
}
func submit(id: Int, iterations: Int) throws {
guard (1...1_000_000).contains(iterations) else {
throw InputError.invalidIterations
}
let job = Calculation(id: id, iterations: iterations)
queue.addOperation { [log] in
log.begin()
let value = sumOfSquares(job)
log.finish(id: job.id, value: value)
}
}
// Call only after the producer has stopped submitting work.
// This blocking method is for the command-line demonstration.
func finishForDemo() -> (
rows: [(id: Int, value: UInt64)], peak: Int
) {
queue.waitUntilAllOperationsAreFinished()
return log.snapshot()
}
}
@main
struct Demo {
static func main() throws {
let limit = 2
let pool = CalculationPool(limit: limit)
try pool.submit(id: 1, iterations: 100_000)
try pool.submit(id: 2, iterations: 200_000)
try pool.submit(id: 3, iterations: 300_000)
let report = pool.finishForDemo()
let rows = report.rows.sorted { $0.id < $1.id }
precondition(rows.map { $0.id } == [1, 2, 3])
precondition(rows.map { $0.value } == [
UInt64(333338333350000),
UInt64(2666686666700000),
UInt64(9000045000050000)
])
precondition(report.peak >= 1 && report.peak <= limit)
for row in rows {
print("Job \(row.id): \(row.value)")
}
print("Observed peak: \(report.peak)")
}
}
main.swift dosyasının bulunduğu dizinden derleyip çalıştırın:
swiftc -swift-version 6 -default-isolation nonisolated \
-parse-as-library main.swift -o calculation-demo
./calculation-demo
Üç sonuç satırı şöyle olmalıdır:
Job 1: 333338333350000
Job 2: 2666686666700000
Job 3: 9000045000050000
Son satır, gözlemlenen tepe değerin 1 veya 2 olduğunu bildirir. Bir sınır çakışmaya izin verir; bunu zorunlu kılmaz. Beklenen toplamlar, tam sayı aritmetiği ve n * (n + 1) * (2 * n + 1) / 6 formülü kullanılarak bağımsız biçimde kontrol edildi. Swift programı incelendi, ancak araştırma ortamında Swift yürütülebilir dosyası kurulu olmadığından derlenemedi.
Bu sınır gerçek hesaplamayı nasıl kapsar?
Her işlem etkinlik sayacını başlatır, hesaplamanın tamamını eşzamanlı olarak yürütür, sonucunu kaydeder ve ardından döner. Bu closure’dan ayrıca bir asenkron hesaplama başlatılmaz. Böylece yürütülen işlemleri sınırlamak, bu hesaplamaları da sınırlar.
Programı uyarlarken bu ilişkiyi koruyun. Bir işlem yalnızca ayrı bir Task oluşturup hemen dönerse, bu görev hesaplamaya devam edebileceği hâlde kuyruk işlemin tamamlandığını görür. Bu durumda işlem sınırı, kısa başlatma closure’larını yönetir. Bu sonuç, belgelenen işlem ömrü ve blok gönderim API’sinden çıkar. Apple’ın OperationQueue genel bakışı ve addOperation(_:) bu unsurları tanımlar.
Günlüğün kilidi yalnızca kısa muhasebe bölümlerini kapsar. sumOfSquares(_:) sırasında kilidi tutmak, hesaplamaya erişimi serileştirir ve amaçlanan çakışmayı önler. Her kilit alma ve bırakma işlemi tek bir eşzamanlı yöntem içinde kalır; bu, Apple’ın NSLock kilidinin onu alan iş parçacığı tarafından açılması gerekliliğiyle uyumludur. Apple’ın NSLock belgeleri bu gerekliliği açıklar.
Bir uyarlamayı kontrol etmek için limit değerini 1 olarak ayarlayıp aynı üç isteği çalıştırın, ardından 2 değerini geri yükleyin. Her iki çalıştırmada da üç sonucun tamamı aynen korunmalıdır. Seri çalıştırma 1 tepe değeri bildirmelidir; ikinci çalıştırma 2 sınırını aşmamalıdır. Bu kontroller, belirli bir zamanlama sırası varsaymadan sonuçların korunmasını ve gözlemlenen hesaplama sınırını sınar.
Masaüstü eylemlerini arayüzü engellemeden bağlayın
Masaüstü hesaplama özelliğini yöneten bileşende tek bir CalculationPool saklayın. Gelen her eylem submit(id:iterations:) yöntemini bir kez çağırır. Eylem işleyicisinin içinde havuzu yeniden oluşturmayın; her yeni örneğin bağımsız bir kuyruğu ve sınırı vardır.
Örnek, komut satırı sürecinin çıkmadan önce sonuçları toplayabilmesi için finishForDemo() yöntemini yalnızca son gönderimden sonra çağırır. Bu yöntem, çağrıldığı iş parçacığını engeller. Bir masaüstü uygulamasında, bunu bir düğme işleyicisinden veya ana aktörden çağırmak yerine her sonucu ayrı ayrı iletin. Apple, waitUntilAllOperationsAreFinished() yönteminin engelleyici davranışını açıkça belgeliyor.
Arayüz entegrasyonu için gönderim API’sini, MainActor üzerinde yalıtılmış ve @Sendable olarak işaretlenmiş bir sonuç geri çağrısıyla genişletin. İşlem içinde sonucu hesapladıktan sonra, bu geri çağrıyı çağırmak için açık bir @MainActor closure’ı içeren Task planlayın. Değişmez sonuç değerini ve geri çağrıyı yakalayın. Bu closure’ın içinde yalnızca görüntüleme güncellemesini tutun; aritmetik işlemi işlem içinde bırakın. Swift hem ana aktör closure söz dizimini hem de gönderilebilir closure yakalamalarını açıklar.
Bu arayüz sürümü, tamamlanmanın ayrıca tanımlanmasını gerektirir: hesaplamanın bitmesi ve sonucunun görüntülenmesi ayrı olaylardır. Komut satırı programı hesaplamaların ve eşzamanlı sonuç kaydının tamamlanmasını bekler. Ayrı olarak planlanmış bir arayüz geri çağrısı daha sonra çalışabilir.
Gönderimler hesaplamaları aşarsa ne olur?
Eşzamanlılık ayarı, yürütülen işlemleri sınırlar; ek işler ise kuyrukta bekleyebilir. Ayrıca örnekteki tamamlanmış sonuç dizisinin büyümesine izin verir. Dolayısıyla bu örnek hesaplama eşzamanlılığını sınırlar, ancak toplam bellek kullanımını veya bekleyen iş sayısını sınırlandırmaz. Apple, kuyruğun işlemleri tamamlanana kadar tuttuğunu belgeliyor. OperationQueue genel bakışı saklama davranışını açıklar.
Masaüstü özelliği sürekli yoğun gönderimler üretebiliyorsa, onu genişletmeden önce bir kabul politikası seçin: bekleyen istek sayısını sabit bir değerle sınırlayıp reddedilen istekleri bildirin, yeni gönderimleri duraklatın veya bu davranış açıkça isteniyorsa bekleyen bir sentetik önizlemeyi daha yeni bir istekle değiştirin. Kabul sayılarını ve tamamlanma güncellemelerini tek bir eşzamanlanmış sahip üzerinden yönetin. Çalışan iş sınırı tek başına bu politikaları uygulayamaz.
Tek tek gelen eşzamanlı istekler için temel tasarım küçüktür: tek bir kuyruk saklayın, sınırını bir kez yapılandırın, her hesaplamayı kendi işleminin içinde tutun ve paylaşılan sonuçları eşzamanlayın. Masaüstü eylemlerini bağlarken bu sınırları koruyun; ardından yalnızca istek geliş biçimi gerektiriyorsa açık bir bekleyen işler politikası ekleyin.
Kaynaklar
- OperationQueue — Apple.
- maxConcurrentOperationCount — Apple.
- addOperation(_:) — Apple.
- waitUntilAllOperationsAreFinished() — Apple.
- NSLock — Apple.
- Sendable — Apple.
- Concurrency — The Swift Programming Language — Swift.org.
- TaskGroup.swift — Swift standart kütüphanesi kaynak kodu ve belgeleri — Swift.org.
- SE-0466: Varsayılan aktör yalıtımı çıkarımını denetleme — Swift.org.