नॉनब्लॉकिंग एपीआई, काम करने का अनुरोध करते हैं. इसके बाद, कंट्रोल को वापस कॉलिंग थ्रेड पर भेज देते हैं, ताकि अनुरोध की गई कार्रवाई पूरी होने से पहले, वह अन्य काम कर सके. ये एपीआई उन मामलों में काम आते हैं जहां अनुरोध किया गया काम जारी हो सकता है या काम पूरा होने से पहले I/O या IPC के पूरा होने, सिस्टम के ऐसे संसाधनों की उपलब्धता या उपयोगकर्ता के इनपुट का इंतज़ार करना पड़ सकता है जिन पर काफ़ी विवाद है. खास तौर पर, अच्छी तरह से डिज़ाइन किए गए एपीआई, प्रोसेस को रद्द करने का तरीका उपलब्ध कराते हैं. साथ ही, ओरिजनल कॉलर की ओर से काम करने की प्रोसेस को रोकते हैं. इससे, सिस्टम की परफ़ॉर्मेंस और बैटरी लाइफ़ को बनाए रखने में मदद मिलती है. ऐसा तब होता है, जब प्रोसेस की अब ज़रूरत नहीं होती.
एसिंक्रोनस एपीआई, नॉनब्लॉकिंग व्यवहार को हासिल करने का एक तरीका है. एसिंक एपीआई, कुछ समय बाद होने वाली कार्रवाई या कॉलबैक को स्वीकार करते हैं. कार्रवाई पूरी होने या कार्रवाई के दौरान होने वाले अन्य इवेंट के बारे में सूचना दी जाती है.
एसिंक्रोनस एपीआई लिखने की दो मुख्य वजहें हैं:
- एक साथ कई कार्रवाइयां करना. इसमें N-1वीं कार्रवाई पूरी होने से पहले, Nवीं कार्रवाई शुरू करनी होती है.
- किसी कार्रवाई के पूरा होने तक, कॉलिंग थ्रेड को ब्लॉक करने से बचें.
Kotlin, स्ट्रक्चर्ड कॉनकरेंसी को बढ़ावा देता है. यह सिद्धांतों और एपीआई की एक सीरीज़ है, जिसे सस्पेंड फ़ंक्शन पर बनाया गया है. यह थ्रेड-ब्लॉकिंग के व्यवहार से कोड के सिंक्रोनस और एसिंक्रोनस एक्ज़ीक्यूशन को अलग करता है. सस्पेंड फ़ंक्शन, नॉनब्लॉकिंग और सिंक्रोनस होते हैं.
फ़ंक्शन निलंबित करना:
- उनकी कॉलिंग थ्रेड को ब्लॉक न करें. इसके बजाय, उनकी एक्ज़ीक्यूशन थ्रेड को लागू करने की जानकारी के तौर पर इस्तेमाल करें. ऐसा तब तक करें, जब तक किसी दूसरी जगह पर चल रहे ऑपरेशनों के नतीजे नहीं मिल जाते.
- सिंक किए गए तरीके से काम करते हैं. साथ ही, इन्हें नॉनब्लॉकिंग एपीआई को कॉल करने वाले व्यक्ति की ज़रूरत नहीं होती. ऐसा इसलिए, क्योंकि ये एपीआई कॉल से शुरू किए गए नॉनब्लॉकिंग काम के साथ-साथ काम करते रहते हैं.
इस पेज पर, डेवलपर की उन बुनियादी उम्मीदों के बारे में बताया गया है जो उन्हें नॉनब्लॉकिंग और एसिंक्रोनस एपीआई का इस्तेमाल करते समय रखनी चाहिए. इसके बाद, Kotlin या Java भाषाओं में, Android प्लैटफ़ॉर्म या Jetpack लाइब्रेरी में इन उम्मीदों को पूरा करने वाले एपीआई बनाने के तरीके बताए गए हैं. अगर आपको कोई शंका है, तो किसी भी नए एपीआई प्लैटफ़ॉर्म के लिए, डेवलपर की उम्मीदों को ज़रूरी शर्तें माना जा सकता है.
एसिंक्रोनस एपीआई के लिए डेवलपर की उम्मीदें
यहां दी गई उम्मीदें, एपीआई को निलंबित न करने के नज़रिए से लिखी गई हैं. हालांकि, अगर कहीं अलग से बताया गया है, तो उसे ध्यान में रखा जाएगा.
कॉलबैक स्वीकार करने वाले एपीआई आम तौर पर एसिंक्रोनस होते हैं
अगर कोई एपीआई ऐसे कॉलबैक को स्वीकार करता है जिसे सिर्फ़ इन-प्लेस में कॉल करने के लिए दस्तावेज़ में शामिल नहीं किया गया है, तो एपीआई को एसिंक्रोनस माना जाता है. इसका मतलब है कि एपीआई कॉल के वापस आने से पहले, इसे सिर्फ़ कॉलिंग थ्रेड से कॉल किया जाता है. साथ ही, एपीआई को यहां दिए गए सेक्शन में बताई गई अन्य सभी शर्तों को पूरा करना चाहिए.
कॉलबैक का एक उदाहरण, मैप या फ़िल्टर करने वाला हाई-ऑर्डर फ़ंक्शन है. यह फ़ंक्शन, किसी कलेक्शन में मौजूद हर आइटम पर मैपर या प्रेडिकेट को लागू करता है. इसके बाद, यह फ़ंक्शन वैल्यू रिटर्न करता है.
एसिंक्रोनस एपीआई को जल्द से जल्द जवाब देना चाहिए
डेवलपर को उम्मीद होती है कि एसिंक एपीआई नॉनब्लॉकिंग हों और ऑपरेशन के लिए अनुरोध शुरू करने के बाद, तुरंत जवाब दें. एसिंक्रोनस एपीआई को कभी भी कॉल किया जा सकता है. साथ ही, एसिंक्रोनस एपीआई को कॉल करने से कभी भी जंक फ़्रेम या एएनआर नहीं होना चाहिए.
कई ऑपरेशन और लाइफ़साइकल सिग्नल, प्लैटफ़ॉर्म या लाइब्रेरी से मांग पर ट्रिगर किए जा सकते हैं. साथ ही, डेवलपर से यह उम्मीद करना कि वह अपने कोड के लिए, सभी संभावित कॉल साइटों की ग्लोबल जानकारी रखे, सही नहीं है. उदाहरण के लिए, View मेज़रमेंट और लेआउट के जवाब में, सिंक्रोनस ट्रांज़ैक्शन में FragmentManager में Fragment जोड़ा जा सकता है. ऐसा तब किया जाता है, जब उपलब्ध जगह (जैसे कि RecyclerView) को भरने के लिए ऐप्लिकेशन का कॉन्टेंट अपने-आप जानकारी भर जाना ज़रूरी हो. इस फ़्रैगमेंट के onStart लाइफ़साइकल कॉलबैक का जवाब देने वाला LifecycleObserver, यहां एक बार स्टार्टअप ऑपरेशन कर सकता है. ऐसा हो सकता है कि यह जैंक से मुक्त ऐनिमेशन का फ़्रेम बनाने के लिए, अहम कोड पाथ पर हो. डेवलपर को हमेशा यह भरोसा होना चाहिए कि इस तरह के लाइफ़साइकल कॉलबैक के जवाब में, किसी भी एसिंक एपीआई को कॉल करने से, फ़्रेम में गड़बड़ी नहीं होगी.
इसका मतलब है कि एसिंक एपीआई को जवाब देने से पहले, बहुत कम काम करना चाहिए. जैसे, अनुरोध और उससे जुड़े कॉलबैक का रिकॉर्ड बनाना और उसे उस एक्ज़ीक्यूशन इंजन के साथ रजिस्टर करना जो ज़्यादा से ज़्यादा काम करता है. अगर किसी एसिंक ऑपरेशन के लिए रजिस्टर करने के लिए आईपीसी की ज़रूरत होती है, तो एपीआई के लागू करने वाले को डेवलपर की इस उम्मीद को पूरा करने के लिए ज़रूरी कदम उठाने चाहिए. इनमें से कोई एक या इससे ज़्यादा मकसद शामिल हो सकते हैं:
- बुनियादी आईपीसी को एकतरफ़ा बाइंडर कॉल के तौर पर लागू करना
- सिस्टम सर्वर में दो-तरफ़ा बाइंडर कॉल करना. इसमें रजिस्ट्रेशन पूरा करने के लिए, ज़्यादा विवादित लॉक लेने की ज़रूरत नहीं होती
- ऐप्लिकेशन प्रोसेस में वर्कर थ्रेड को अनुरोध पोस्ट करना, ताकि आईपीसी पर रजिस्ट्रेशन को ब्लॉक किया जा सके
एसिंक्रोनस एपीआई को शून्य वैल्यू दिखानी चाहिए और सिर्फ़ अमान्य तर्कों के लिए गड़बड़ी का मैसेज दिखाना चाहिए
एसिंक एपीआई को, अनुरोध की गई कार्रवाई के सभी नतीजे, दिए गए कॉलबैक को भेजने चाहिए. इससे डेवलपर को, सफलता और गड़बड़ी को ठीक करने के लिए एक ही कोड पाथ लागू करने की अनुमति मिलती है.
एसिंक एपीआई, आर्ग्युमेंट के लिए शून्य की जांच कर सकते हैं और NullPointerException या यह जांच कर सकते हैं कि दिए गए आर्ग्युमेंट मान्य सीमा के अंदर हैं और IllegalArgumentException को थ्रो कर सकते हैं. उदाहरण के लिए, अगर किसी फ़ंक्शन में 0 से 1f के बीच की वैल्यू स्वीकार की जाती है, तो फ़ंक्शन यह जांच कर सकता है कि पैरामीटर इस रेंज में है या नहीं. अगर पैरामीटर इस रेंज से बाहर है, तो वह IllegalArgumentException दिखा सकता है. इसके अलावा, String की जांच यह देखने के लिए की जा सकती है कि वह मान्य फ़ॉर्मैट के मुताबिक है या नहीं. जैसे, सिर्फ़ अक्षरों और अंकों का इस्तेमाल किया गया है या नहीं.float (ध्यान रखें कि सिस्टम सर्वर को कभी भी ऐप्लिकेशन प्रोसेस पर भरोसा नहीं करना चाहिए! किसी भी सिस्टम सर्विस को, सिस्टम सर्विस में इन जांचों को डुप्लीकेट करना चाहिए.)
अन्य सभी गड़बड़ियों की जानकारी, दिए गए कॉलबैक को दी जानी चाहिए. इसमें ये शामिल हैं, लेकिन इनके अलावा और भी चीज़ें शामिल हो सकती हैं:
- अनुरोध की गई कार्रवाई पूरी नहीं हो सकी
- कार्रवाई पूरी करने के लिए ज़रूरी अनुमति या मंज़ूरी न होने पर सुरक्षा से जुड़ी अपवाद की स्थितियां
- कार्रवाई करने के लिए तय किया गया कोटा खत्म हो गया है
- कार्रवाई करने के लिए, ऐप्लिकेशन प्रोसेस "फ़ोरग्राउंड" में नहीं है
- ज़रूरी हार्डवेयर डिसकनेक्ट हो गया है
- नेटवर्क की समस्याएं
- टाइम आउट की संख्या
- Binder की प्रोसेस बंद हो गई है या रिमोट प्रोसेस उपलब्ध नहीं है
एसिंक्रोनस एपीआई में, अनुरोध रद्द करने का तरीका उपलब्ध होना चाहिए
एसिंक एपीआई को, चालू ऑपरेशन को यह बताने का तरीका देना चाहिए कि कॉलर को अब नतीजे की परवाह नहीं है. रद्द करने की इस कार्रवाई से दो चीज़ों का पता चलना चाहिए:
कॉल करने वाले व्यक्ति की ओर से दिए गए कॉल बैक के हार्ड रेफ़रंस रिलीज़ किए जाने चाहिए
एसिंक एपीआई को दिए गए कॉलबैक में, बड़े ऑब्जेक्ट ग्राफ़ के हार्ड रेफ़रंस शामिल हो सकते हैं. साथ ही, उस कॉलबैक के हार्ड रेफ़रंस को होल्ड करने वाला मौजूदा काम, उन ऑब्जेक्ट ग्राफ़ को गार्बेज इकट्ठा होने से रोक सकता है. रद्द करने पर इन कॉलबैक रेफ़रंस को रिलीज़ करके, इन ऑब्जेक्ट ग्राफ़ को गार्बेज कलेक्शन के लिए बहुत पहले ही उपलब्ध कराया जा सकता है. ऐसा तब होता है, जब काम को पूरा होने की अनुमति दी जाती है.
कॉल करने वाले व्यक्ति के लिए काम करने वाला एक्ज़ीक्यूशन इंजन, उस काम को बंद कर सकता है
एसिंक एपीआई कॉल से शुरू किए गए काम में, बिजली की खपत या अन्य सिस्टम संसाधनों की लागत ज़्यादा हो सकती है. कॉल करने वालों को यह सिग्नल देने की अनुमति देने वाले एपीआई कि अब इस काम की ज़रूरत नहीं है, ताकि सिस्टम के ज़्यादा संसाधनों का इस्तेमाल होने से पहले ही उस काम को रोका जा सके.
कैश मेमोरी में सेव किए गए या फ़्रीज़ किए गए ऐप्लिकेशन के लिए ध्यान देने वाली खास बातें
एसिंक्रोनस एपीआई डिज़ाइन करते समय, इन बातों का ध्यान रखें. इनमें सिस्टम प्रोसेस में शुरू होने वाले और ऐप्लिकेशन को डिलीवर किए जाने वाले कॉलबैक शामिल हैं:
- प्रोसेस और ऐप्लिकेशन का लाइफ़साइकल: ऐसा हो सकता है कि मैसेज पाने वाले ऐप्लिकेशन की प्रोसेस, कैश की गई स्थिति में हो.
- कैश किए गए ऐप्लिकेशन फ़्रीज़र: ऐसा हो सकता है कि ईमेल पाने वाले व्यक्ति के ऐप्लिकेशन की प्रोसेस फ़्रीज़ हो गई हो.
जब कोई ऐप्लिकेशन प्रोसेस, कैश मेमोरी में सेव की गई स्थिति में पहुंच जाती है, तो इसका मतलब है कि वह उपयोगकर्ता को दिखने वाले किसी भी कॉम्पोनेंट को होस्ट नहीं कर रही है. जैसे, गतिविधियां और सेवाएं. ऐप्लिकेशन को मेमोरी में सेव रखा जाता है, ताकि वह फिर से उपयोगकर्ता को दिख सके. हालांकि, इस दौरान उसे काम नहीं करना चाहिए. ज़्यादातर मामलों में, जब कोई ऐप्लिकेशन कैश मेमोरी में सेव की गई स्थिति में होता है, तब आपको ऐप्लिकेशन के कॉलबैक भेजने बंद कर देने चाहिए. जब ऐप्लिकेशन कैश मेमोरी में सेव की गई स्थिति से बाहर आ जाए, तब आपको कॉलबैक भेजने फिर से शुरू कर देने चाहिए. ऐसा इसलिए, ताकि कैश मेमोरी में सेव किए गए ऐप्लिकेशन की प्रोसेस में काम न हो.
कैश किए गए ऐप्लिकेशन को भी फ़्रीज़ किया जा सकता है. किसी ऐप्लिकेशन को फ़्रीज़ करने पर, उसे सीपीयू का कोई भी समय नहीं मिलता. इसलिए, वह कोई भी काम नहीं कर पाता. उस ऐप्लिकेशन के लिए रजिस्टर किए गए सभी कॉल बैक को बफ़र किया जाता है. ऐप्लिकेशन के अनफ़्रीज़ होने पर, उन्हें डिलीवर किया जाता है.
ऐप्लिकेशन के कॉलबैक के लिए बफ़र किए गए लेन-देन, ऐप्लिकेशन के अनफ़्रीज़ होने और उन्हें प्रोसेस करने के समय तक पुराने हो सकते हैं. बफ़र की एक तय सीमा होती है. अगर यह सीमा पार हो जाती है, तो ईमेल पाने वाला ऐप्लिकेशन क्रैश हो जाएगा. ऐप्लिकेशन को पुराने इवेंट से बचाने या उनके बफ़र को ओवरफ़्लो होने से रोकने के लिए, उनकी प्रोसेस फ़्रीज़ होने पर ऐप्लिकेशन के कॉलबैक डिस्पैच न करें.
समीक्षा की जा रही है:
- ऐप्लिकेशन की प्रोसेस कैश मेमोरी में सेव होने के दौरान, ऐप्लिकेशन के कॉलबैक भेजने की सुविधा को बंद कर दें.
- ऐप्लिकेशन की प्रोसेस फ़्रीज़ होने के दौरान, आपको ऐप्लिकेशन के कॉलबैक भेजने की सुविधा को ज़रूर रोकना होगा.
स्टेट ट्रैकिंग
ऐप्लिकेशन के कैश मेमोरी में सेव होने या उससे बाहर निकलने की स्थिति को ट्रैक करने के लिए:
mActivityManager.addOnUidImportanceListener(
new UidImportanceListener() { ... },
IMPORTANCE_CACHED);
यह ट्रैक करने के लिए कि ऐप्लिकेशन कब फ़्रीज़ या अनफ़्रीज़ किए गए:
IBinder binder = <...>;
binder.addFrozenStateChangeCallback(executor, callback);
ऐप्लिकेशन के कॉल बैक भेजने की सुविधा फिर से शुरू करने की रणनीतियां
जब ऐप्लिकेशन, कैश मेमोरी में सेव होने वाली स्थिति या फ़्रीज़ होने वाली स्थिति में चला जाता है, तब ऐप्लिकेशन के कॉलबैक भेजने की सुविधा को रोक दिया जाता है. जब ऐप्लिकेशन इन स्थितियों से बाहर आ जाता है, तब आपको ऐप्लिकेशन के रजिस्टर किए गए कॉलबैक भेजने की सुविधा को फिर से शुरू करना चाहिए. ऐसा तब तक करना चाहिए, जब तक ऐप्लिकेशन अपने कॉलबैक को रजिस्टर नहीं कर लेता या ऐप्लिकेशन की प्रोसेस बंद नहीं हो जाती.
उदाहरण के लिए:
IBinder binder = <...>;
bool shouldSendCallbacks = true;
binder.addFrozenStateChangeCallback(executor, (who, state) -> {
if (state == IBinder.FrozenStateChangeCallback.STATE_FROZEN) {
shouldSendCallbacks = false;
} else if (state == IBinder.FrozenStateChangeCallback.STATE_UNFROZEN) {
shouldSendCallbacks = true;
}
});
इसके अलावा, RemoteCallbackList का इस्तेमाल किया जा सकता है. यह फ़्रीज़ होने पर, टारगेट प्रोसेस को कॉलबैक डिलीवर नहीं करता है.
उदाहरण के लिए:
RemoteCallbackList<IInterface> rc =
new RemoteCallbackList.Builder<IInterface>(
RemoteCallbackList.FROZEN_CALLEE_POLICY_DROP)
.setExecutor(executor)
.build();
rc.register(callback);
rc.broadcast((callback) -> callback.foo(bar));
callback.foo() को सिर्फ़ तब शुरू किया जाता है, जब प्रोसेस फ़्रीज़ न की गई हो.
ऐप्लिकेशन, अक्सर कॉल बैक का इस्तेमाल करके मिले अपडेट को, मौजूदा स्थिति के स्नैपशॉट के तौर पर सेव करते हैं. मान लें कि ऐप्लिकेशन के लिए एक काल्पनिक एपीआई है, जो बैटरी के बचे हुए प्रतिशत की निगरानी करता है:
interface BatteryListener {
void onBatteryPercentageChanged(int newPercentage);
}
मान लें कि ऐप्लिकेशन के फ़्रीज़ होने पर, स्थिति में बदलाव करने वाले कई इवेंट होते हैं. ऐप्लिकेशन के अनफ़्रीज़ होने पर, आपको ऐप्लिकेशन को सिर्फ़ सबसे हाल की स्थिति देनी चाहिए. साथ ही, स्थिति में हुए अन्य पुराने बदलावों को हटा देना चाहिए. ऐप्लिकेशन को अनफ़्रीज़ करने के तुरंत बाद, यह डिलीवरी होनी चाहिए, ताकि ऐप्लिकेशन "अप-टू-डेट" हो सके. इसे इस तरह से किया जा सकता है:
RemoteCallbackList<IInterface> rc =
new RemoteCallbackList.Builder<IInterface>(
RemoteCallbackList.FROZEN_CALLEE_POLICY_ENQUEUE_MOST_RECENT)
.setExecutor(executor)
.build();
rc.register(callback);
rc.broadcast((callback) -> callback.onBatteryPercentageChanged(value));
कुछ मामलों में, ऐप्लिकेशन को डिलीवर की गई आखिरी वैल्यू को ट्रैक किया जा सकता है, ताकि ऐप्लिकेशन को अनफ़्रीज़ करने के बाद, उसे उसी वैल्यू की सूचना न देनी पड़े.
स्टेट को ज़्यादा मुश्किल डेटा के तौर पर दिखाया जा सकता है. नेटवर्क इंटरफ़ेस के बारे में सूचना पाने के लिए, ऐप्लिकेशन के लिए एक काल्पनिक एपीआई पर विचार करें:
interface NetworkListener {
void onAvailable(Network network);
void onLost(Network network);
void onChanged(Network network);
}
किसी ऐप्लिकेशन के लिए सूचनाएं रोकने के दौरान, आपको उन नेटवर्क और राज्यों के बारे में पता होना चाहिए जहां ऐप्लिकेशन को पिछली बार देखा गया था. फिर से शुरू होने पर, यह सुझाव दिया जाता है कि ऐप्लिकेशन को इन नेटवर्क के बारे में सूचना दें: पुराने नेटवर्क जो बंद हो गए थे, नए नेटवर्क जो उपलब्ध हो गए हैं, और मौजूदा नेटवर्क जिनकी स्थिति बदल गई है. सूचना इसी क्रम में दें.
उन नेटवर्क के बारे में ऐप्लिकेशन को सूचना न दें जो कॉल बैक के रुकने के दौरान उपलब्ध कराए गए थे और फिर बंद कर दिए गए थे. ऐप्लिकेशन को उन इवेंट की पूरी जानकारी नहीं मिलनी चाहिए जो फ़्रीज़ होने के दौरान हुए थे. साथ ही, एपीआई के दस्तावेज़ में यह वादा नहीं किया जाना चाहिए कि लाइफ़साइकल की स्थितियों के बाहर भी इवेंट स्ट्रीम बिना किसी रुकावट के डिलीवर की जाएंगी. इस उदाहरण में, अगर ऐप्लिकेशन को नेटवर्क की उपलब्धता की लगातार निगरानी करनी है, तो उसे लाइफ़साइकल की ऐसी स्थिति में रहना होगा जिससे वह कैश मेमोरी में सेव न हो या फ़्रीज़ न हो.
समीक्षा के दौरान, आपको उन इवेंट को एक साथ जोड़ना चाहिए जो सूचनाएं रोकने के बाद और फिर से शुरू करने से पहले हुए थे. साथ ही, रजिस्टर किए गए ऐप्लिकेशन के कॉलबैक को कम शब्दों में मौजूदा स्थिति के बारे में बताना चाहिए.
डेवलपर के दस्तावेज़ के लिए ध्यान रखने वाली बातें
एसिंक इवेंट की डिलीवरी में देरी हो सकती है. ऐसा इसलिए हो सकता है, क्योंकि भेजने वाले ने पिछले सेक्शन में दिखाए गए समय के लिए डिलीवरी रोक दी हो या पाने वाले ऐप्लिकेशन को इवेंट को समय पर प्रोसेस करने के लिए, डिवाइस के ज़रूरी संसाधन न मिले हों.
डेवलपर को इस बात का अनुमान लगाने से रोकना कि उनके ऐप्लिकेशन को किसी इवेंट की सूचना कब मिली और वह इवेंट असल में कब हुआ.
एपीआई को निलंबित करने के बारे में डेवलपर की उम्मीदें
Kotlin के स्ट्रक्चर्ड कॉनकरेंसी के बारे में जानने वाले डेवलपर, सस्पेंड करने वाले किसी भी एपीआई से ये उम्मीदें रखते हैं:
निलंबित करने वाले फ़ंक्शन को, वापस लौटने या थ्रो करने से पहले, उससे जुड़ा सारा काम पूरा करना चाहिए
नॉनब्लॉकिंग ऑपरेशंस के नतीजे, फ़ंक्शन की सामान्य रिटर्न वैल्यू के तौर पर दिखाए जाते हैं. साथ ही, गड़बड़ियों की जानकारी अपवादों के ज़रिए दी जाती है. (इसका अक्सर मतलब होता है कि कॉलबैक पैरामीटर ज़रूरी नहीं हैं.)
सस्पेंड फ़ंक्शन को सिर्फ़ कॉलबैक पैरामीटर को इन-प्लेस पर लागू करना चाहिए
निलंबित किए गए फ़ंक्शन को, वैल्यू वापस भेजने से पहले उससे जुड़ा सारा काम पूरा कर लेना चाहिए. इसलिए, उन्हें कभी भी दिए गए कॉलबैक या अन्य फ़ंक्शन पैरामीटर को लागू नहीं करना चाहिए. साथ ही, निलंबित किए गए फ़ंक्शन के वापस आने के बाद, उसका रेफ़रंस बनाए नहीं रखना चाहिए.
कॉलबैक पैरामीटर स्वीकार करने वाले सस्पेंड फ़ंक्शन को कॉन्टेक्स्ट-प्रिज़र्विंग होना चाहिए. हालांकि, अगर किसी अन्य दस्तावेज़ में इसके बारे में कुछ और बताया गया है, तो उस दस्तावेज़ में दी गई जानकारी को माना जाएगा
सस्पेंड फ़ंक्शन में किसी फ़ंक्शन को कॉल करने से, वह कॉलर के CoroutineContext में चलता है. निलंबित किए गए फ़ंक्शन को, वैल्यू वापस करने या अपवाद थ्रो करने से पहले, उससे जुड़ा सारा काम पूरा करना चाहिए. साथ ही, उसे सिर्फ़ कॉलबैक पैरामीटर को इन-प्लेस पर लागू करना चाहिए. इसलिए, डिफ़ॉल्ट रूप से यह माना जाता है कि इस तरह के सभी कॉलबैक, कॉलिंग CoroutineContext पर भी चलाए जाते हैं. इसके लिए, उससे जुड़े डिस्पैचर का इस्तेमाल किया जाता है. अगर एपीआई का मकसद कॉलिंग CoroutineContext से बाहर कॉलबैक चलाना है, तो इस व्यवहार के बारे में साफ़ तौर पर बताया जाना चाहिए.
सस्पेंड फ़ंक्शन में kotlinx.coroutines जॉब को रद्द करने की सुविधा होनी चाहिए
सस्पेंड किए गए किसी भी फ़ंक्शन को, kotlinx.coroutines के मुताबिक जॉब रद्द करने की सुविधा के साथ काम करना चाहिए. अगर किसी ऑपरेशन के लिए कॉल करने का काम रद्द कर दिया जाता है, तो फ़ंक्शन को जल्द से जल्द CancellationException के साथ फिर से शुरू होना चाहिए, ताकि कॉल करने वाला व्यक्ति जल्द से जल्द साफ़ कर सके और जारी रख सके. इसे suspendCancellableCoroutine और kotlinx.coroutines के निलंबित करने वाले अन्य एपीआई अपने-आप मैनेज करते हैं. लाइब्रेरी के लागू करने के तरीके में आम तौर पर suspendCoroutine का सीधे तौर पर इस्तेमाल नहीं किया जाना चाहिए, क्योंकि यह डिफ़ॉल्ट रूप से रद्द करने के इस तरीके के साथ काम नहीं करता.
बैकग्राउंड (नॉन-मेन या यूज़र इंटरफ़ेस थ्रेड) पर ब्लॉक करने का काम करने वाले सस्पेंड फ़ंक्शन में, इस्तेमाल किए गए डिस्पैचर को कॉन्फ़िगर करने का तरीका होना चाहिए
थ्रेड स्विच करने के लिए, ब्लॉकिंग फ़ंक्शन को पूरी तरह से सस्पेंड करने का सुझाव नहीं दिया जाता.
सस्पेंड फ़ंक्शन को कॉल करने पर, अतिरिक्त थ्रेड नहीं बननी चाहिए. साथ ही, डेवलपर को उस काम को पूरा करने के लिए, अपनी थ्रेड या थ्रेड पूल उपलब्ध कराने की अनुमति देनी चाहिए. उदाहरण के लिए, कोई कंस्ट्रक्टर ऐसी CoroutineContext स्वीकार कर सकता है जिसका इस्तेमाल क्लास के तरीकों के लिए बैकग्राउंड में काम करने के लिए किया जाता है.
सस्पेंड किए गए ऐसे फ़ंक्शन जो सिर्फ़ उस डिस्पैचर पर स्विच करने के लिए, वैकल्पिक CoroutineContext या Dispatcher पैरामीटर स्वीकार करते हैं ताकि ब्लॉकिंग का काम किया जा सके, उन्हें इसके बजाय ब्लॉकिंग के लिए इस्तेमाल होने वाले फ़ंक्शन को दिखाना चाहिए. साथ ही, कॉल करने वाले डेवलपर को यह सुझाव देना चाहिए कि वे withContext को कॉल करने के लिए, अपने कॉल का इस्तेमाल करें, ताकि काम को चुने गए डिस्पैचर पर भेजा जा सके.
कोरूटीन लॉन्च करने वाली क्लास
कोरूटीन लॉन्च करने वाली क्लास में, लॉन्च करने की उन कार्रवाइयों को पूरा करने के लिए CoroutineScope होना चाहिए. स्ट्रक्चर्ड कंकरेंसी के सिद्धांतों का पालन करने का मतलब है कि स्कोप को पाने और मैनेज करने के लिए, स्ट्रक्चरल पैटर्न का इस्तेमाल किया जाए.
किसी दूसरे स्कोप में एक साथ कई टास्क लॉन्च करने वाली क्लास लिखने से पहले, अन्य पैटर्न पर विचार करें:
class MyClass {
private val requests = Channel<MyRequest>(Channel.UNLIMITED)
suspend fun handleRequests() {
coroutineScope {
for (request in requests) {
// Allow requests to be processed concurrently;
// alternatively, omit the [launch] and outer [coroutineScope]
// to process requests serially
launch {
processRequest(request)
}
}
}
}
fun submitRequest(request: MyRequest) {
requests.trySend(request).getOrThrow()
}
}
suspend fun को एक साथ कई काम करने के लिए उपलब्ध कराने से, कॉलर अपने कॉन्टेक्स्ट में ऑपरेशन शुरू कर सकता है. इससे MyClass को CoroutineScope मैनेज करने की ज़रूरत नहीं पड़ती. अनुरोधों को प्रोसेस करने के लिए, क्रम से लगाने की प्रोसेस आसान हो जाती है. साथ ही, स्थिति को अक्सर क्लास प्रॉपर्टी के बजाय handleRequests के लोकल वैरिएबल के तौर पर इस्तेमाल किया जा सकता है. ऐसा न करने पर, अतिरिक्त सिंक्रनाइज़ेशन की ज़रूरत होगी.
कोरूटीन को मैनेज करने वाली क्लास को close और cancel तरीके दिखाने चाहिए
जिन क्लास में कोरूटीन को लागू करने की जानकारी दी जाती है उनमें, एक साथ चल रहे उन टास्क को शट डाउन करने का तरीका होना चाहिए. ऐसा इसलिए, ताकि वे पैरंट स्कोप में अनियंत्रित तरीके से एक साथ काम न करें. आम तौर पर, ऐसा CoroutineContext के लिए Job बनाकर किया जाता है:
private val myJob = Job(parent = `CoroutineContext`[Job])
private val myScope = CoroutineScope(`CoroutineContext` + myJob)
fun cancel() {
myJob.cancel()
}
उपयोगकर्ता के कोड को ऑब्जेक्ट के ज़रिए किए जा रहे किसी भी काम के पूरा होने का इंतज़ार करने की अनुमति देने के लिए, join() तरीका भी उपलब्ध कराया जा सकता है.
(इसमें किसी ऑपरेशन को रद्द करके किया गया क्लीनअप भी शामिल हो सकता है.)
suspend fun join() {
myJob.join()
}
टर्मिनल ऑपरेशन का नामकरण
उन तरीकों के लिए इस्तेमाल किया गया नाम जो किसी ऑब्जेक्ट के ऐसे टास्क को शट डाउन करें जो अब भी चल रहे हैं, उन्हें बंद करने के तरीके के बारे में जानकारी देनी चाहिए:
close() का इस्तेमाल तब करें, जब चल रही कार्रवाइयां पूरी हो सकती हैं, लेकिन close() को कॉल करने के बाद कोई नई कार्रवाई शुरू नहीं की जा सकती.
cancel() का इस्तेमाल तब करें, जब प्रोसेस पूरी होने से पहले ही उसे रद्द किया जा सकता हो.
cancel() को कॉल करने के बाद, कोई नई कार्रवाई शुरू नहीं की जा सकती.
क्लास कंस्ट्रक्टर, CoroutineScope के बजाय CoroutineContext स्वीकार करते हैं
जब ऑब्जेक्ट को सीधे तौर पर दिए गए पैरंट स्कोप में लॉन्च करने की अनुमति नहीं होती है, तो कंस्ट्रक्टर पैरामीटर के तौर पर CoroutineScope के इस्तेमाल की सुविधा काम नहीं करती:
// Don't do this
class MyClass(scope: CoroutineScope) {
private val myJob = Job(parent = scope.`CoroutineContext`[Job])
private val myScope = CoroutineScope(scope.`CoroutineContext` + myJob)
// ... the [scope] constructor parameter is never used again
}
CoroutineScope एक ऐसा रैपर बन जाता है जिसकी ज़रूरत नहीं होती और जो गुमराह करता है. कुछ मामलों में, इसे सिर्फ़ कंस्ट्रक्टर पैरामीटर के तौर पर पास करने के लिए बनाया जा सकता है. हालांकि, बाद में इसे हटा दिया जाता है:
// Don't do this; just pass the context
val myObject = MyClass(CoroutineScope(parentScope.`CoroutineContext` + Dispatchers.IO))
CoroutineContext पैरामीटर डिफ़ॉल्ट रूप से EmptyCoroutineContext पर सेट होते हैं
जब कोई वैकल्पिक CoroutineContext पैरामीटर, एपीआई सरफेस में दिखता है, तो डिफ़ॉल्ट वैल्यू CoroutineContext सेंटिनल होनी चाहिए.Empty`CoroutineContext` इससे एपीआई के व्यवहार को बेहतर तरीके से कंपोज़ किया जा सकता है, क्योंकि कॉलर से मिली Empty`CoroutineContext` वैल्यू को डिफ़ॉल्ट वैल्यू के तौर पर ही माना जाता है:
class MyOuterClass(
`CoroutineContext`: `CoroutineContext` = Empty`CoroutineContext`
) {
private val innerObject = MyInnerClass(`CoroutineContext`)
// ...
}
class MyInnerClass(
`CoroutineContext`: `CoroutineContext` = Empty`CoroutineContext`
) {
private val job = Job(parent = `CoroutineContext`[Job])
private val scope = CoroutineScope(`CoroutineContext` + job)
// ...
}