Android के लिए, एसिंक्रोनस और नॉन-ब्लॉकिंग एपीआई के दिशा-निर्देश

नॉनब्लॉकिंग एपीआई, काम करने का अनुरोध करते हैं. इसके बाद, कंट्रोल को वापस कॉलिंग थ्रेड पर भेज देते हैं, ताकि अनुरोध की गई कार्रवाई पूरी होने से पहले, वह अन्य काम कर सके. ये एपीआई उन मामलों में काम आते हैं जहां अनुरोध किया गया काम जारी हो सकता है या काम पूरा होने से पहले I/O या IPC के पूरा होने का इंतज़ार करना पड़ सकता है. इसके अलावा, सिस्टम के ऐसे संसाधनों की उपलब्धता का इंतज़ार करना पड़ सकता है जिन पर ज़्यादा विवाद है या काम शुरू होने से पहले उपयोगकर्ता के इनपुट का इंतज़ार करना पड़ सकता है. खास तौर पर, अच्छी तरह से डिज़ाइन किए गए एपीआई, प्रोसेस को रद्द करने का तरीका उपलब्ध कराते हैं. साथ ही, ओरिजनल कॉलर की ओर से काम को पूरा होने से रोकते हैं. इससे, सिस्टम की परफ़ॉर्मेंस और बैटरी लाइफ़ को बनाए रखने में मदद मिलती है. ऐसा तब होता है, जब प्रोसेस की अब ज़रूरत नहीं होती.

एसिंक्रोनस एपीआई, नॉनब्लॉकिंग व्यवहार को हासिल करने का एक तरीका है. एसिंक एपीआई, कुछ समय बाद पूरा होने वाले ऑपरेशन के लिए, जारी रखने या कॉलबैक करने की सुविधा देते हैं. ऑपरेशन पूरा होने या ऑपरेशन के दौरान होने वाले अन्य इवेंट के बारे में सूचना दी जाती है.

एसिंक्रोनस एपीआई लिखने के दो मुख्य मकसद होते हैं:

  • एक साथ कई कार्रवाइयां करना. इसमें Nवीं कार्रवाई, N-1वीं कार्रवाई पूरी होने से पहले शुरू होनी चाहिए.
  • किसी ऑपरेशन के पूरा होने तक, कॉलिंग थ्रेड को ब्लॉक करने से बचना.

Kotlin, स्ट्रक्चर्ड कॉनकरेंसी को बढ़ावा देता है. यह सिद्धांतों और एपीआई की एक सीरीज़ है, जिसे सस्पेंड फ़ंक्शन पर बनाया गया है. ये फ़ंक्शन, थ्रेड-ब्लॉकिंग के व्यवहार से कोड के सिंक्रोनस और एसिंक्रोनस एक्ज़ीक्यूशन को अलग करते हैं. सस्पेंड फ़ंक्शन, नॉनब्लॉकिंग और सिंक्रोनस होते हैं.

फ़ंक्शन निलंबित करना:

  • उनकी कॉलिंग थ्रेड को ब्लॉक न करें. इसके बजाय, उनकी एक्ज़ीक्यूशन थ्रेड को लागू करने की जानकारी के तौर पर इस्तेमाल करें. ऐसा तब तक करें, जब तक किसी दूसरी जगह पर चल रहे ऑपरेशन के नतीजे नहीं मिल जाते.
  • सिंक्रोनस तरीके से काम करते हैं. साथ ही, इन्हें नॉनब्लॉकिंग एपीआई को कॉल करने वाले व्यक्ति की ज़रूरत नहीं होती. ऐसा इसलिए, ताकि एपीआई कॉल से शुरू होने वाले नॉनब्लॉकिंग काम के साथ-साथ, ये एक साथ काम करना जारी रख सकें.

इस पेज पर, डेवलपर की उन बुनियादी उम्मीदों के बारे में बताया गया है जो उन्हें नॉनब्लॉकिंग और एसिंक्रोनस एपीआई का इस्तेमाल करते समय रखनी चाहिए. इसके बाद, Kotlin या Java भाषाओं में, Android प्लैटफ़ॉर्म या Jetpack लाइब्रेरी में इन उम्मीदों को पूरा करने वाले एपीआई बनाने के तरीके बताए गए हैं. अगर आपको कोई शक है, तो डेवलपर की उम्मीदों को किसी भी नए एपीआई प्लैटफ़ॉर्म के लिए ज़रूरी शर्तें माना जा सकता है.

एसिंक्रोनस एपीआई के लिए डेवलपर की उम्मीदें

यहां दी गई उम्मीदें, एपीआई को निलंबित न करने के नज़रिए से लिखी गई हैं. हालांकि, अगर कहीं और बताया गया है, तो उसे ध्यान में रखा जाएगा.

कॉलबैक स्वीकार करने वाले एपीआई आम तौर पर एसिंक्रोनस होते हैं

अगर कोई एपीआई ऐसे कॉलबैक को स्वीकार करता है जिसे सिर्फ़ इन-प्लेस में कॉल करने के लिए दस्तावेज़ में शामिल नहीं किया गया है, तो एपीआई को एसिंक्रोनस माना जाता है. इसका मतलब है कि एपीआई कॉल के वापस आने से पहले, इसे सिर्फ़ कॉलिंग थ्रेड से कॉल किया जाता है. साथ ही, एपीआई को यहां दिए गए सेक्शन में बताई गई अन्य सभी शर्तों को पूरा करना चाहिए.

कॉलबैक का एक उदाहरण, मैप या फ़िल्टर फ़ंक्शन है. इसे सिर्फ़ इन-प्लेस में कॉल किया जाता है. यह फ़ंक्शन, किसी कलेक्शन में मौजूद हर आइटम पर मैपर या प्रेडिकेट को लागू करता है. इसके बाद, आइटम को वापस भेजता है.

एसिंक्रोनस एपीआई को जल्द से जल्द जवाब देना चाहिए

डेवलपर को उम्मीद होती है कि एसिंक एपीआई नॉनब्लॉकिंग हों और ऑपरेशन के लिए अनुरोध शुरू करने के बाद, तुरंत जवाब दें. एसिंक्रोनस एपीआई को कभी भी कॉल किया जा सकता है. साथ ही, एसिंक्रोनस एपीआई को कॉल करने से कभी भी जंक फ़्रेम या एएनआर नहीं होना चाहिए.

कई ऑपरेशन और लाइफ़साइकल सिग्नल, प्लैटफ़ॉर्म या लाइब्रेरी से मांग पर ट्रिगर किए जा सकते हैं. साथ ही, डेवलपर से यह उम्मीद करना कि वह अपने कोड के लिए, सभी संभावित कॉल साइटों की ग्लोबल जानकारी रखे, सही नहीं है. उदाहरण के लिए, View मेज़रमेंट और लेआउट के जवाब में, सिंक्रोनस लेन-देन में FragmentManager में Fragment जोड़ा जा सकता है. ऐसा तब किया जाता है, जब उपलब्ध जगह (जैसे कि RecyclerView) को भरने के लिए ऐप्लिकेशन का कॉन्टेंट पॉप्युलेट करना ज़रूरी हो. इस फ़्रैगमेंट के onStart लाइफ़साइकल कॉलबैक का जवाब देने वाला LifecycleObserver, यहां एक बार स्टार्टअप ऑपरेशन कर सकता है. यह जंक से मुक्त ऐनिमेशन का फ़्रेम बनाने के लिए, अहम कोड पाथ पर हो सकता है. डेवलपर को हमेशा यह भरोसा होना चाहिए कि इस तरह के लाइफ़साइकल कॉलबैक के जवाब में, किसी भी एसिंक एपीआई को कॉल करने से, फ़्रेम में गड़बड़ी नहीं होगी.

इसका मतलब है कि एसिंक एपीआई को जवाब देने से पहले, बहुत कम काम करना चाहिए. जैसे, अनुरोध और उससे जुड़े कॉलबैक का रिकॉर्ड बनाना और उसे उस एक्ज़ीक्यूशन इंजन के साथ रजिस्टर करना जो ज़्यादा से ज़्यादा काम करता है. अगर किसी एसिंक ऑपरेशन के लिए रजिस्टर करने के लिए आईपीसी की ज़रूरत होती है, तो एपीआई के लागू करने वाले को डेवलपर की इस उम्मीद को पूरा करने के लिए ज़रूरी कदम उठाने चाहिए. इसमें इनमें से कोई एक या इससे ज़्यादा चीज़ें शामिल हो सकती हैं:

  • बुनियादी आईपीसी को एकतरफ़ा बाइंडर कॉल के तौर पर लागू करना
  • सिस्टम सर्वर में दो-तरफ़ा बाइंडर कॉल करना. इसमें रजिस्ट्रेशन पूरा करने के लिए, ज़्यादा विवादित लॉक लेने की ज़रूरत नहीं होती
  • ऐप्लिकेशन प्रोसेस में वर्कर थ्रेड को अनुरोध पोस्ट करना, ताकि आईपीसी पर ब्लॉकिंग रजिस्ट्रेशन किया जा सके

एसिंक्रोनस एपीआई को शून्य वैल्यू दिखानी चाहिए और सिर्फ़ अमान्य तर्कों के लिए गड़बड़ी का मैसेज दिखाना चाहिए

एसिंक एपीआई को, अनुरोध की गई कार्रवाई के सभी नतीजे, दिए गए कॉलबैक को भेजने चाहिए. इससे डेवलपर को, सफलता और गड़बड़ी को ठीक करने के लिए एक ही कोड पाथ लागू करने की सुविधा मिलती है.

एसिंक एपीआई, शून्य के लिए आर्ग्युमेंट की जांच कर सकते हैं और NullPointerException थ्रो कर सकते हैं. इसके अलावा, वे यह भी जांच सकते हैं कि दिए गए आर्ग्युमेंट मान्य सीमा के अंदर हैं या नहीं और NullPointerException थ्रो कर सकते हैं.IllegalArgumentException उदाहरण के लिए, अगर कोई फ़ंक्शन 0 से 1f की रेंज में float स्वीकार करता है, तो फ़ंक्शन यह जांच कर सकता है कि पैरामीटर इस रेंज में है या नहीं. अगर पैरामीटर इस रेंज से बाहर है, तो वह IllegalArgumentException दिखा सकता है. इसके अलावा, String की लंबाई की जांच यह देखने के लिए की जा सकती है कि वह मान्य फ़ॉर्मैट के मुताबिक है या नहीं. जैसे, सिर्फ़ अक्षरों और अंकों का इस्तेमाल किया गया है या नहीं. (ध्यान रखें कि सिस्टम सर्वर को कभी भी ऐप्लिकेशन प्रोसेस पर भरोसा नहीं करना चाहिए! किसी भी सिस्टम सर्विस को, सिस्टम सर्विस में इन जांचों को डुप्लीकेट करना चाहिए.)

अन्य सभी गड़बड़ियों की सूचना, दिए गए कॉलबैक को दी जानी चाहिए. इसमें ये शामिल हैं, लेकिन इनके अलावा और भी चीज़ें शामिल हो सकती हैं:

  • अनुरोध की गई कार्रवाई पूरी नहीं की जा सकी
  • कार्रवाई पूरी करने के लिए ज़रूरी अनुमति या अनुमति न होने पर सुरक्षा से जुड़ी अपवाद की स्थितियां
  • कार्रवाई करने के लिए तय किया गया कोटा खत्म हो गया है
  • कार्रवाई करने के लिए, ऐप्लिकेशन प्रोसेस "फ़ोरग्राउंड" में नहीं है
  • ज़रूरी हार्डवेयर डिसकनेक्ट हो गया है
  • नेटवर्क की समस्याएं
  • टाइम आउट की संख्या
  • Binder की प्रोसेस बंद हो गई है या रिमोट प्रोसेस उपलब्ध नहीं है

एसिंक्रोनस एपीआई में, अनुरोध रद्द करने का तरीका उपलब्ध होना चाहिए

एसिंक एपीआई को, चालू ऑपरेशन को यह बताने का तरीका देना चाहिए कि कॉलर को अब नतीजे की परवाह नहीं है. रद्द करने की इस कार्रवाई से दो चीज़ों का पता चलना चाहिए:

कॉल करने वाले व्यक्ति की ओर से दिए गए कॉल बैक के हार्ड रेफ़रंस रिलीज़ किए जाने चाहिए

एसिंक एपीआई को दिए गए कॉलबैक में, बड़े ऑब्जेक्ट ग्राफ़ के हार्ड रेफ़रंस शामिल हो सकते हैं. साथ ही, उस कॉलबैक के हार्ड रेफ़रंस को होल्ड करने वाला मौजूदा काम, उन ऑब्जेक्ट ग्राफ़ को गार्बेज कलेक्शन से बचा सकता है. रद्द करने पर इन कॉलबैक रेफ़रंस को रिलीज़ करके, इन ऑब्जेक्ट ग्राफ़ को गार्बेज कलेक्शन के लिए बहुत पहले ही उपलब्ध कराया जा सकता है. ऐसा तब नहीं होता, जब काम को पूरा होने दिया जाता है.

कॉल करने वाले व्यक्ति के लिए काम करने वाला एक्ज़ीक्यूशन इंजन, उस काम को बंद कर सकता है

एसिंक एपीआई कॉल से शुरू किए गए काम में, बिजली की खपत या अन्य सिस्टम संसाधनों की लागत ज़्यादा हो सकती है. कॉल करने वालों को यह सूचना देने की अनुमति देने वाले एपीआई कि अब इस काम की ज़रूरत नहीं है. इससे सिस्टम के ज़्यादा संसाधनों का इस्तेमाल होने से पहले ही, उस काम को रोका जा सकता है.

कैश मेमोरी में सेव किए गए या फ़्रीज़ किए गए ऐप्लिकेशन के लिए ध्यान देने वाली खास बातें

एसिंक्रोनस एपीआई डिज़ाइन करते समय, इन बातों का ध्यान रखें. इन एपीआई में कॉलबैक, सिस्टम प्रोसेस से शुरू होते हैं और ऐप्लिकेशन को डिलीवर किए जाते हैं:

  1. प्रोसेस और ऐप्लिकेशन का लाइफ़साइकल: ऐसा हो सकता है कि ऐप्लिकेशन की प्रोसेस को कैश मेमोरी में सेव किया गया हो.
  2. कैश किए गए ऐप्लिकेशन फ़्रीज़र: ऐसा हो सकता है कि ईमेल पाने वाले व्यक्ति के ऐप्लिकेशन की प्रोसेस फ़्रीज़ हो गई हो.

जब कोई ऐप्लिकेशन प्रोसेस, कैश मेमोरी में सेव की गई स्थिति में पहुंच जाती है, तो इसका मतलब है कि वह उपयोगकर्ता को दिखने वाले किसी भी कॉम्पोनेंट को सक्रिय रूप से होस्ट नहीं कर रही है. जैसे, गतिविधियां और सेवाएं. ऐप्लिकेशन को मेमोरी में सेव रखा जाता है, ताकि उपयोगकर्ता को फिर से दिखने पर उसे तुरंत चालू किया जा सके. हालांकि, इस दौरान उसे कोई काम नहीं करना चाहिए. ज़्यादातर मामलों में, जब कोई ऐप्लिकेशन कैश मेमोरी में सेव की गई स्थिति में होता है, तब आपको ऐप्लिकेशन के कॉलबैक भेजने बंद कर देने चाहिए. जब ऐप्लिकेशन कैश मेमोरी में सेव की गई स्थिति से बाहर आ जाए, तब आपको उन्हें फिर से शुरू कर देना चाहिए. ऐसा इसलिए, ताकि कैश मेमोरी में सेव किए गए ऐप्लिकेशन की प्रोसेस में काम न हो.

कैश किए गए ऐप्लिकेशन को भी फ़्रीज़ किया जा सकता है. किसी ऐप्लिकेशन को फ़्रीज़ करने पर, उसे सीपीयू का कोई भी समय नहीं मिलता. इसलिए, वह कोई भी काम नहीं कर पाता. उस ऐप्लिकेशन के लिए रजिस्टर किए गए सभी कॉल बैक को बफ़र किया जाता है. साथ ही, ऐप्लिकेशन के अनफ़्रीज़ होने पर उन्हें डिलीवर किया जाता है.

ऐप्लिकेशन के कॉलबैक में बफ़र किए गए लेन-देन, ऐप्लिकेशन के अनफ़्रीज़ होने और उन्हें प्रोसेस करने के समय तक पुराने हो सकते हैं. बफ़र की एक तय सीमा होती है. अगर यह सीमा पार हो जाती है, तो ईमेल पाने वाला ऐप्लिकेशन क्रैश हो जाएगा. ऐप्लिकेशन को पुराने इवेंट से बचाने या उनके बफ़र को ओवरफ़्लो होने से रोकने के लिए, उनकी प्रोसेस फ़्रीज़ होने पर ऐप्लिकेशन के कॉलबैक डिस्पैच न करें.

समीक्षा की जा रही है:

  • ऐप्लिकेशन की प्रोसेस कैश मेमोरी में सेव होने के दौरान, आपको ऐप्लिकेशन के कॉलबैक भेजने की सुविधा को बंद कर देना चाहिए.
  • ऐप्लिकेशन की प्रोसेस फ़्रीज़ होने के दौरान, आपको ऐप्लिकेशन के कॉलबैक भेजने की सुविधा ज़रूर रोकनी चाहिए.

स्टेट ट्रैकिंग

ऐप्लिकेशन के कैश मेमोरी में सेव होने या उससे बाहर निकलने की स्थिति को ट्रैक करने के लिए:

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)

    // ...
}