Mac ऐप में स्थानीय एसिंक्रोनस गणनाएँ सबमिशन क्रम में पूरी करनी हों, तो एक सिंक्रोनस main-actor सबमिशन विधि इस्तेमाल करें, जो हर टास्क को अपने पूर्ववर्ती के पीछे जोड़ती है। केवल actor isolation से क्रम की यह गारंटी नहीं मिलती।
यह वॉकथ्रू पूर्णांकों की कृत्रिम गणनाओं के लिए एक छोटी कतार बनाता है। यह ठीक-ठीक परिभाषित करता है कि सबमिशन कब होता है, सस्पेंशन पॉइंट के पार क्रम बनाए रखता है, हर गणना का परिणाम लौटाता है, और पहले का कोई जॉब विफल होने पर बाद के जॉब जारी रहने देता है।
कठोर सबमिशन क्रम का क्या अर्थ है?
मान लें कि डेस्कटॉप कैलकुलेटर जॉब A, B और C सबमिट करता है। अपेक्षित अनुबंध यह है: A की गणना पूरी होने के बाद ही B की गणना शुरू हो, और B पूरी होने के बाद ही C शुरू हो। तीन स्वतंत्र रूप से शुरू किए गए टास्क को वर्णमाला क्रम में पूरा होने की प्रतीक्षा करने से केवल उनके परिणामों का अवलोकन क्रमबद्ध होगा।
सबमिशन की सीमा मायने रखती है। इस कार्यान्वयन में सबमिशन तब होता है जब main-actor-isolated submit विधि चलती है और श्रृंखला में अगली कड़ी जोड़ती है। एक ही main-actor संदर्भ से लगातार किए गए सिंक्रोनस कॉल, उनके बीच सस्पेंड हुए बिना, A, B, C का क्रम स्थापित करते हैं।
यदि स्वतंत्र टास्क एक साथ सबमिशन का अनुरोध करते हैं, तो उनके पहुँचने का क्रम निर्दिष्ट नहीं होता। कतार उस क्रम को बनाए रखती है जिसमें उसकी सबमिशन विधि उन्हें स्वीकार करती है। यदि आपके ऐप को उन प्रोड्यूसरों के बीच कोई खास क्रम चाहिए, तो सबमिट करने से पहले वह क्रम तय करें—उदाहरण के लिए, सीमित बैच को स्पष्ट रूप से क्रमबद्ध ऐरे में एकत्र करके।
इस डिज़ाइन में हिसाब-किताब के छोटे चरण के लिए main actor का उपयोग होता है। Apple, MainActor को एक singleton actor के रूप में परिभाषित करता है, जिसका executor main dispatch queue के समतुल्य है। नीचे की वास्तविक गणना एक अलग worker actor की ज़िम्मेदारी है। MainActor का दस्तावेज़ीकरण
केवल actor या serial operation queue पर्याप्त क्यों नहीं हैं?
Actor, actor-isolated काम को एक समय में एक हिस्से के रूप में चलाता है, लेकिन async विधि सस्पेंड हो सकती है और किसी दूसरे कॉल को आगे बढ़ने दे सकती है। Swift के actor डिज़ाइन में यह भी स्पष्ट है कि किसी actor की प्रतीक्षा कर रहे टास्क के मूल await क्रम में चलने की गारंटी नहीं है। इसलिए पूरी async गणना को actor के भीतर रखने भर से पहले पूरा हो, फिर अगला शुरू हो—यह व्यवहार स्थापित नहीं होता। SE-0306: Actors
अलग-अलग Task इंस्टेंस बनाने से उनके बीच कोई निर्भरता नहीं बनती। टास्क बनने के बाद चलना शुरू कर सकते हैं; उनके हैंडल रखने से आप उनका इंतज़ार या उन्हें रद्द कर सकते हैं, लेकिन इससे उनके चलने का क्रम तय नहीं होता। Task का दस्तावेज़ीकरण
Foundation क्रम तय करने का एक अलग तरीका देता है। OperationQueue, readiness, priority और dependencies के अनुसार शेड्यूल करता है; Apple का सुझाव है कि जब कोई विशिष्ट निष्पादन क्रम मायने रखता हो, तो dependencies का उपयोग करें। maxConcurrentOperationCount को 1 पर सेट करने से समवर्ती निष्पादन सीमित होता है, लेकिन क्रम की आवश्यकता के लिए अब भी स्पष्ट dependencies चाहिए। OperationQueue का दस्तावेज़ीकरण
आपके ऐप की मौजूदा ज़रूरत के अनुसार तरीका चुनें:
| स्थिति | उपयुक्त तरीका | क्रम किससे तय होता है? |
|---|---|---|
| एक async फ़ंक्शन में एक ज्ञात बैच | लूप, जो आगे बढ़ने से पहले हर गणना का इंतज़ार करता है | क्रमिक नियंत्रण प्रवाह |
| डेस्कटॉप कार्रवाइयों से समय-समय पर आने वाले जॉब | टास्क चेन वाली सिंक्रोनस सबमिशन विधि | हर गणना अपने पूर्ववर्ती के completion gate का इंतज़ार करती है |
| पहले से मौजूद Foundation ऑपरेशन | कतार में जोड़ने से पहले कॉन्फ़िगर की गई dependencies | हर ऑपरेशन पिछले ऑपरेशन के समाप्त होने पर निर्भर करता है |
यहाँ टास्क चेन उपयोगी है क्योंकि गणनाएँ बाद में चलती हैं, जबकि सबमिशन तुरंत एक हैंडल लौटा देता है। यदि जॉब आने वाले नहीं हैं और बैच तय है, तो क्रमिक लूप में आमतौर पर कम कोड लगेगा।
ऐसी कतार बनाएँ जो पूरी async गणनाओं का इंतज़ार करे
कतार tail नाम का एक निजी, nonthrowing completion task रखती है। हर सबमिशन मौजूदा tail को कैप्चर करता है, उसका इंतज़ार करने वाला गणना टास्क बनाता है, और tail को एक नए टास्क से बदल देता है, जो गणना पूरी होने का निरीक्षण करता है।
Completion task, result का इंतज़ार करता है, जो टास्क पूरा होने पर सफलता या विफलता—दोनों में से किसी एक को दर्शाता है। इससे असफल गणना अगला जॉब जारी कर सकती है, जबकि उसकी त्रुटि उसके अपने हैंडल से मिलती रहती है। कॉलर अब भी हैंडल के ज़रिए वह त्रुटि प्राप्त कर सकते हैं। Task परिणाम का दस्तावेज़ीकरण
नीचे Mac और Swift 6 टूलचेन के लिए एक पूरा कमांड-लाइन प्रदर्शन है। इसे OrderedDemo.swift नाम से सहेजें। स्रोत की जाँच की गई, लेकिन इस शोध परिवेश में इसे कंपाइल नहीं किया जा सका क्योंकि Swift कंपाइलर उपलब्ध नहीं था।
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)")
}
}
Terminal में, सहेजे गए प्रोग्राम की डायरेक्टरी से उसे कंपाइल करके चलाएँ:
swiftc -swift-version 6 -parse-as-library OrderedDemo.swift -o OrderedDemo
./OrderedDemo
उदाहरण के इनपुट और dependency chain के अनुसार अपेक्षित आउटपुट यह है:
start A
finish A
start B
finish B
start C
finish C
Answers: [9, 16, 25]
Assertions लौटाए गए मानों और गणना की सीमाओं—दोनों की जाँच करते हैं। सबमिशन क्रम में हैंडल का इंतज़ार करते हुए केवल परिणाम प्रिंट करने से यह स्थापित नहीं होगा कि गणनाएँ भी उसी क्रम में चलीं।
Worker जानबूझकर Task.yield() कॉल करता है, ताकि गणना में सस्पेंड होने का अवसर शामिल हो। Yield करने से दूसरे टास्क चल सकते हैं, हालाँकि executor उसी टास्क को तुरंत फिर से शुरू कर सकता है। इसलिए कतार का क्रम उसकी dependencies से आता है, इस धारणा से नहीं कि yield करने पर टास्क बदल जाएगा। Task yield का दस्तावेज़ीकरण
सस्पेंशन के दौरान चेन क्रम कैसे बनाए रखती है?
तीन बातें इस गारंटी को स्थापित करती हैं। पहली, submit में कोई suspension point नहीं है, इसलिए वह tail को कैप्चर करके बदलते समय कोई अन्य main-actor सबमिशन बीच में नहीं आ सकता। Swift दस्तावेज़ित करता है कि suspension point के बिना actor-isolated कोड, actor के किसी अन्य टास्क द्वारा उस क्रम में रुकावट डाले बिना चलता है। Concurrency: Actors
दूसरी, हर जॉब गणना closure को चलाने से पहले अपने कैप्चर किए हुए पूर्ववर्ती का इंतज़ार करता है। Nonthrowing task की value तक पहुँचना उस टास्क के पूरा होने की प्रतीक्षा करता है। बाद का जॉब प्रतीक्षा से जुड़ा अपना हिसाब-किताब पहले कर सकता है, लेकिन उसकी गणना समय से पहले शुरू नहीं हो सकती। Nonthrowing Task value का दस्तावेज़ीकरण
तीसरी, पूर्ववर्ती gate तभी पूरा होता है जब पहले का जॉब अपना परिणाम या त्रुटि दे देता है। कड़ियों को पीछे की ओर देखने से हर स्वीकार किए गए सबमिशन का क्रम स्थापित होता है: C, B के completion gate का इंतज़ार करता है; वह gate B का इंतज़ार करता है; और B, A के completion gate का इंतज़ार करता है।
यह क्रम की गारंटी हर सबमिट किए गए closure के भीतर await किए गए काम के लिए है। यदि कोई closure एक और unstructured task शुरू करके उसका इंतज़ार किए बिना लौट आता है, तो वह अतिरिक्त काम closure के बाद भी जारी रह सकता है। लौटने से पहले सभी आवश्यक गणना चरणों को await किए गए ऑपरेशन में शामिल करें।
डेस्कटॉप इंटीग्रेशन के लिए, संबंधित कार्रवाइयाँ संभालने वाले main-actor controller या model में कतार का एक इंस्टेंस रखें। उसी संदर्भ से सीधे submit कॉल करें, फिर पूरा होने का पता लगाने के लिए लौटाए गए हैंडल का उपयोग करें। पहले एक स्वतंत्र टास्क बनाकर उसके शुरू होने के बाद ही सबमिट करने से सबमिशन की सीमा उस टास्क के बाद में होने वाले निष्पादन पर चली जाएगी।
त्रुटियों और रद्दीकरण का व्यवहार कैसा होना चाहिए?
यह कार्यान्वयन विफलता के बाद आगे जारी रहने की नीति अपनाता है। विफल जॉब अपनी त्रुटि बनाए रखता है, उसका completion gate पूरा होता है और अगला स्वीकार किया गया जॉब गणना के लिए पात्र हो जाता है। पहला त्रुटि मिलने पर बाकी परिणाम छोड़ने के बजाय, प्रदर्शन हर हैंडल के result की जाँच करता है।
विफलता की सरल जाँच के लिए, B का इनपुट 4 से बदलकर 1001 करें। Worker गुणा करने से पहले उसे अस्वीकार कर देता है। अपेक्षित उत्तरों को [9, 25] और अपेक्षित ट्रेस को start A, finish A, start B, start C, finish C में बदलें। प्रोग्राम को विफलता प्रिंट करनी चाहिए और फिर भी C की गणना करनी चाहिए। इनपुट सीमा एक उदाहरण के लिए चुना गया अनुबंध है, ताकि यह अंकगणित छोटा रहे।
रद्दीकरण सहयोगात्मक होता है। कतार अपने पूर्ववर्ती का इंतज़ार करने के बाद रद्दीकरण जाँचती है, और worker yield करने के बाद फिर से जाँचता है। रद्दीकरण का अनुरोध किए जाने पर Task.checkCancellation() CancellationError फेंकता है। Task checkCancellation का दस्तावेज़ीकरण
निष्पादन से पहले रद्दीकरण जाँचने के लिए, C सबमिट करने के तुरंत बाद और परिणाम लूप से पहले b.cancel() कॉल करें। इन बिना रुकावट वाले main-actor सबमिशनों में, B अपनी गणना में अभी तक प्रवेश नहीं कर सकता। अपेक्षित उत्तरों को [9, 25] के अनुसार बदलें और अपेक्षित ट्रेस से B की दोनों प्रविष्टियाँ हटा दें। रद्दीकरण बताने से पहले भी B, A के completion gate का इंतज़ार करेगा; फिर C आगे बढ़ेगा।
Completion gates को निजी रखें। वे चेन के हिसाब-किताब का प्रतिनिधित्व करते हैं, जबकि लौटाए गए हैंडल अलग-अलग गणनाओं का। किसी जॉब को रद्द करने का अर्थ यह नहीं कि उससे पहले की गणना रुक जानी चाहिए या उसके चलते रहने के दौरान बाद की गणनाएँ शुरू हो जानी चाहिए।
यदि आपके ऐप को किसी एक विफलता के बाद पूरा बैच रोकना हो, तो उसके लिए स्पष्ट बैच नीति चाहिए। Gate को त्रुटियाँ आगे भेजने के लिए बदलने से यह बदल जाएगा कि बाद के कौन से जॉब चलेंगे और उनके परिणामों की रिपोर्ट कैसे करनी होगी।
Foundation dependencies का उपयोग कब करना चाहिए?
यदि डेस्कटॉप ऐप पहले से Foundation Operation ऑब्जेक्ट के रूप में काम को दर्शाता है, तो कतार में जोड़ने से पहले हर ऑपरेशन को उसके पूर्ववर्ती पर निर्भर बनाएँ। Foundation में dependency तभी पूरी मानी जाती है जब पूर्ववर्ती ऑपरेशन समाप्त हो जाए, चाहे वह सफल हुआ हो या विफल; सफलता पर निर्भर व्यवहार के लिए परिणामों को अलग से संभालना होगा। Operation का दस्तावेज़ीकरण
Swift async फ़ंक्शन को उस मॉडल से जोड़ते समय सावधानी रखें। कोई synchronous operation यदि Task शुरू करके तुरंत लौट जाए, तो वह टास्क के पूरे जीवनकाल को नहीं दर्शाता। एक custom asynchronous operation को अपने निष्पादन और समाप्ति की स्थिति ठीक तरह संभालनी चाहिए, जिसमें आवश्यक notifications भी शामिल हैं, ताकि निर्भर ऑपरेशन अपेक्षित काम पूरा होने तक प्रतीक्षा करें। Apple asynchronous operations और finish-state reporting के अंतर्गत इन ज़िम्मेदारियों का दस्तावेज़ीकरण करता है। Operation lifecycle का दस्तावेज़ीकरण
यहाँ विकसित स्थानीय async calculator के लिए, task chain अनुबंध को स्पष्ट रखती है: सबमिशन सिंक्रोनस रूप से स्वीकार करें, पूर्ववर्ती के पूरा होने का इंतज़ार करें, पूरी गणना चलाएँ और उसका परिणाम उपलब्ध कराएँ। कतार को डेस्कटॉप कार्रवाइयों से जोड़ते समय इन सीमाओं को बनाए रखें, और लौटाए गए मानों के साथ शुरू और समाप्त होने के ट्रेस से भी निष्पादन की पुष्टि करें।
स्रोत
- MainActor — Apple
- SE-0306: Actors — Swift
- Task — Apple
- OperationQueue — Apple
- result — Apple
- yield() — Apple
- Concurrency — Swift
- value: Nonthrowing task — Apple
- checkCancellation() — Apple
- Operation — Apple