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

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

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

असाइनोक्रोनस एपीआई लिखने की दो मुख्य वजहें हैं:

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

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

सस्पेंड किए गए फ़ंक्शन:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  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 पैरामीटर दिखता है, तो डिफ़ॉल्ट वैल्यू 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)

    // ...
}