دستورالعمل‌های API همگام‌سازی و غیرانسدادی Android

APIهای غیر مسدودکننده درخواست انجام کار را می‌دهند و سپس کنترل را به نخ فراخوانی‌کننده برمی‌گردانند تا بتواند قبل از اتمام عملیات درخواستی، کار دیگری را انجام دهد. این APIها برای مواردی مفید هستند که کار درخواستی ممکن است در حال انجام باشد یا ممکن است نیاز به انتظار برای تکمیل I/O یا IPC، در دسترس بودن منابع سیستم با اختلاف زیاد یا ورودی کاربر قبل از ادامه کار داشته باشد. APIهای به خصوص با طراحی خوب، راهی برای لغو عملیات در حال انجام و توقف انجام کار از طرف فراخوانی‌کننده اصلی ارائه می‌دهند و سلامت سیستم و عمر باتری را در زمانی که دیگر نیازی به عملیات نیست، حفظ می‌کنند.

APIهای ناهمگام یکی از راه‌های دستیابی به رفتار غیر مسدودکننده هستند. APIهای ناهمگام نوعی از ادامه یا فراخوانی را می‌پذیرند که هنگام تکمیل عملیات یا سایر رویدادها در طول پیشرفت عملیات، مطلع می‌شوند.

دو انگیزه اصلی برای نوشتن یک API ناهمزمان وجود دارد:

  • اجرای همزمان چندین عملیات، که در آن یک عملیات Nام باید قبل از اتمام عملیات N-1ام آغاز شود.
  • اجتناب از مسدود کردن یک نخ فراخوانی تا زمان تکمیل عملیات.

کاتلین به شدت از همزمانی ساختاریافته (structured concurrency) حمایت می‌کند، مجموعه‌ای از اصول و APIها که بر اساس توابع suspend ساخته شده‌اند و اجرای همزمان و غیرهمزمان کد را از رفتار مسدودسازی نخ جدا می‌کنند. توابع suspend غیرمسدودکننده و همزمان هستند.

توابع تعلیق:

  • نخ فراخوانی آنها را مسدود نکنید و در عوض، نخ اجرای آنها را به عنوان جزئیات پیاده‌سازی ارائه دهید، در حالی که منتظر نتایج عملیات‌های اجرا شده در جای دیگر هستید.
  • به صورت همزمان اجرا شوند و نیازی نباشد که فراخواننده یک API غیر مسدودکننده، همزمان با کار غیر مسدودکننده‌ای که توسط فراخوانی API آغاز شده است، به اجرای خود ادامه دهد.

این صفحه جزئیات حداقل انتظارات اولیه‌ای را که توسعه‌دهندگان می‌توانند هنگام کار با APIهای غیرمسدودکننده و ناهمزمان با خیال راحت رعایت کنند، شرح می‌دهد و به دنبال آن مجموعه‌ای از دستورالعمل‌ها برای نوشتن APIهایی که این انتظارات را در زبان‌های کاتلین یا جاوا، در پلتفرم اندروید یا کتابخانه‌های Jetpack برآورده می‌کنند، ارائه می‌دهد. در صورت شک، انتظارات توسعه‌دهنده را به عنوان الزامات هر سطح API جدید در نظر بگیرید.

انتظارات توسعه‌دهندگان برای APIهای ناهمگام

انتظارات زیر از دیدگاه APIهای غیر معلق نوشته شده‌اند، مگر اینکه خلاف آن ذکر شده باشد.

APIهایی که فراخوانی‌های برگشتی را می‌پذیرند معمولاً غیرهمزمان هستند.

اگر یک API فراخوانی برگشتی را بپذیرد که مستند نشده باشد که فقط درجا فراخوانی شود (یعنی فقط توسط نخ فراخوانی‌کننده قبل از بازگشت فراخوانی API فراخوانی شود)، فرض می‌شود که API ناهمزمان است و آن API باید تمام انتظارات دیگر مستند شده در بخش‌های بعدی را برآورده کند.

نمونه‌ای از یک تابع فراخوانی که فقط درجا فراخوانی می‌شود، یک تابع نگاشت یا فیلتر مرتبه بالاتر است که قبل از بازگرداندن، یک نگاشتگر یا گزاره را روی هر آیتم در یک مجموعه فراخوانی می‌کند.

API های ناهمزمان باید در اسرع وقت بازگردند

توسعه‌دهندگان انتظار دارند که APIهای ناهمگام (async) مسدودکننده نباشند و پس از شروع درخواست برای عملیات، به سرعت برگردند. فراخوانی یک API ناهمگام (async) باید همیشه در هر زمانی ایمن باشد و فراخوانی یک API ناهمگام هرگز نباید منجر به فریم‌های نامنظم یا ANR شود.

بسیاری از عملیات و سیگنال‌های چرخه حیات می‌توانند توسط پلتفرم یا کتابخانه‌ها بر اساس تقاضا فعال شوند و انتظار اینکه یک توسعه‌دهنده دانش جهانی از تمام سایت‌های فراخوانی بالقوه برای کد خود را داشته باشد، ناپایدار است. به عنوان مثال، یک Fragment می‌تواند در یک تراکنش همزمان در پاسخ به اندازه‌گیری و طرح‌بندی View به FragmentManager اضافه شود، زمانی که محتوای برنامه باید برای پر کردن فضای موجود (مانند RecyclerView ) پر شود. یک LifecycleObserver که به فراخوانی onStart چرخه حیات این قطعه پاسخ می‌دهد، ممکن است به طور منطقی عملیات راه‌اندازی یک‌باره را در اینجا انجام دهد و این ممکن است در یک مسیر کد حیاتی برای تولید یک فریم انیمیشن بدون مشکل باشد. یک توسعه‌دهنده همیشه باید مطمئن باشد که فراخوانی هر API ناهمزمان در پاسخ به این نوع فراخوانی‌های چرخه حیات، علت یک فریم مشکل‌دار نخواهد بود.

این بدان معناست که کاری که توسط یک API ناهمزمان قبل از بازگشت انجام می‌شود باید بسیار سبک باشد؛ ایجاد یک رکورد از درخواست و فراخوانی مرتبط و ثبت آن در موتور اجرایی که حداکثر کار را انجام می‌دهد. اگر ثبت برای یک عملیات ناهمزمان نیاز به IPC داشته باشد، پیاده‌سازی API باید هر اقدام لازم را برای برآورده کردن این انتظار توسعه‌دهنده انجام دهد. این ممکن است شامل یک یا چند مورد از موارد زیر باشد:

  • پیاده‌سازی یک IPC اساسی به عنوان یک فراخوانی binder یک‌طرفه
  • برقراری یک فراخوانی اتصال دوطرفه به سرور سیستم که در آن تکمیل ثبت نام نیازی به گرفتن قفل بسیار رقابتی نداشته باشد
  • ارسال درخواست به یک thread کارگر در فرآیند برنامه برای انجام ثبت مسدودسازی از طریق IPC

API های ناهمزمان باید void را برگردانند و فقط برای آرگومان های نامعتبر throw کنند.

APIهای ناهمگام باید تمام نتایج عملیات درخواستی را به تابع فراخوانی ارائه شده گزارش دهند. این به توسعه‌دهنده اجازه می‌دهد تا یک مسیر کد واحد برای موفقیت و مدیریت خطا پیاده‌سازی کند.

APIهای ناهمگام ممکن است آرگومان‌ها را برای تهی بودن بررسی کرده و NullPointerException را پرتاب کنند، یا بررسی کنند که آرگومان‌های ارائه شده در محدوده معتبری باشند و IllegalArgumentException را پرتاب کنند. به عنوان مثال، برای تابعی که یک float در محدوده 0 تا 1f را می‌پذیرد، تابع ممکن است بررسی کند که پارامتر در این محدوده است و اگر خارج از محدوده باشد IllegalArgumentException را پرتاب کند، یا ممکن است یک String کوتاه برای مطابقت با یک قالب معتبر مانند alphanumerics-only بررسی شود. (به یاد داشته باشید که سرور سیستم هرگز نباید به فرآیند برنامه اعتماد کند! هر سرویس سیستمی باید این بررسی‌ها را در خود سرویس سیستم تکرار کند.)

تمام خطاهای دیگر باید به تابع فراخوانی ارائه شده گزارش شوند. این شامل موارد زیر می‌شود، اما محدود به آنها نیست:

  • خرابی ترمینال عملیات درخواستی
  • استثنائات امنیتی برای فقدان مجوز یا مجوزهای لازم برای تکمیل عملیات
  • از سهمیه تعیین شده برای انجام عملیات تجاوز شده است
  • فرآیند برنامه به اندازه کافی "پیش‌زمینه" برای انجام عملیات نیست
  • سخت‌افزار مورد نیاز قطع شده است
  • خرابی‌های شبکه
  • تایم اوت‌ها
  • خرابی بایندر یا عدم دسترسی به فرآیند از راه دور

API های ناهمزمان باید مکانیزم لغو را ارائه دهند

APIهای ناهمگام باید راهی ارائه دهند تا به یک عملیات در حال اجرا نشان دهند که فراخواننده دیگر به نتیجه اهمیتی نمی‌دهد. این عملیات لغو باید دو چیز را نشان دهد:

ارجاعات سخت به فراخوانی‌های برگشتی ارائه شده توسط فراخوانی‌کننده باید حذف شوند.

فراخوانی‌های برگشتی ارائه شده به APIهای ناهمگام ممکن است حاوی ارجاعات سخت به گراف‌های شیء بزرگ باشند، و کار مداومی که ارجاع سخت به آن فراخوانی برگشتی را در خود نگه می‌دارد، می‌تواند از جمع‌آوری زباله توسط آن گراف‌های شیء جلوگیری کند. با انتشار این ارجاعات فراخوانی برگشتی در هنگام لغو، این گراف‌های شیء ممکن است خیلی زودتر از زمانی که به کار اجازه اجرا تا زمان تکمیل داده می‌شود، واجد شرایط جمع‌آوری زباله شوند.

موتور اجرایی که برای فراخواننده کار انجام می‌دهد، ممکن است آن کار را متوقف کند.

کاری که توسط فراخوانی‌های async API آغاز می‌شود، ممکن است هزینه بالایی در مصرف برق یا سایر منابع سیستم داشته باشد. APIهایی که به فراخوانی‌کنندگان اجازه می‌دهند زمانی که دیگر نیازی به این کار نیست، اعلام کنند، اجازه می‌دهند آن کار قبل از اینکه بتواند منابع سیستم بیشتری را مصرف کند، متوقف شود.

ملاحظات ویژه برای برنامه‌های ذخیره شده یا فریز شده

هنگام طراحی APIهای ناهمزمان که در آن‌ها فراخوانی‌های برگشتی از یک فرآیند سیستمی سرچشمه می‌گیرند و به برنامه‌ها تحویل داده می‌شوند، موارد زیر را در نظر بگیرید:

  1. فرآیندها و چرخه حیات برنامه : فرآیند برنامه گیرنده ممکن است در حالت ذخیره شده باشد.
  2. فریز کردن برنامه‌های ذخیره‌شده : ممکن است فرآیند برنامه گیرنده فریز شده باشد.

وقتی یک فرآیند برنامه وارد حالت ذخیره شده می‌شود، به این معنی است که به طور فعال میزبان هیچ مؤلفه قابل مشاهده توسط کاربر مانند فعالیت‌ها و سرویس‌ها نیست. برنامه در حافظه نگه داشته می‌شود تا در صورت نیاز دوباره قابل مشاهده توسط کاربر باشد، اما در این مدت نباید کاری انجام دهد. در بیشتر موارد، باید وقتی برنامه وارد حالت ذخیره شده می‌شود، ارسال فراخوانی‌های برنامه را متوقف کنید و وقتی برنامه از حالت ذخیره شده خارج می‌شود، آن را از سر بگیرید تا باعث ایجاد کار در فرآیندهای برنامه ذخیره شده نشود.

یک برنامه‌ی ذخیره‌شده در حافظه‌ی پنهان (cache) نیز ممکن است مسدود شود. وقتی یک برنامه مسدود می‌شود، زمان پردازنده‌ی آن صفر می‌شود و قادر به انجام هیچ کاری نیست. هرگونه فراخوانی به callbackهای ثبت‌شده‌ی آن برنامه بافر شده و پس از رفع انسداد برنامه، تحویل داده می‌شود.

تراکنش‌های بافر شده به فراخوانی‌های برنامه ممکن است تا زمانی که برنامه از حالت انجماد خارج شده و آنها را پردازش می‌کند، قدیمی باشند. بافر محدود است و اگر سرریز شود، باعث خرابی برنامه گیرنده می‌شود. برای جلوگیری از شلوغ شدن برنامه‌ها با رویدادهای قدیمی یا سرریز شدن بافرهای آنها، فراخوانی‌های برنامه را در حالی که فرآیند آنها منجمد است، ارسال نکنید.

در حال بررسی:

  • شما باید در نظر داشته باشید که ارسال فراخوانی‌های برنامه را در حین ذخیره شدن فرآیند برنامه، متوقف کنید.
  • شما باید ارسال فراخوانی‌های برنامه را در حالی که فرآیند برنامه متوقف شده است، متوقف کنید.

ردیابی ایالتی

برای ردیابی زمان ورود یا خروج برنامه‌ها از حالت کش:

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() فقط در صورتی فراخوانی می‌شود که فرآیند متوقف نشده باشد.

برنامه‌ها اغلب به‌روزرسانی‌هایی را که با استفاده از callbackها دریافت می‌کنند، به عنوان تصویری از آخرین وضعیت ذخیره می‌کنند. یک API فرضی را برای برنامه‌ها در نظر بگیرید تا درصد باتری باقی‌مانده را کنترل کنند:

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));

در برخی موارد، می‌توانید آخرین مقدار تحویل داده شده به برنامه را پیگیری کنید تا برنامه پس از رفع انسداد، نیازی به اطلاع‌رسانی در مورد همان مقدار نداشته باشد.

وضعیت می‌تواند به صورت داده‌های پیچیده‌تری بیان شود. یک API فرضی را برای اطلاع‌رسانی برنامه‌ها از رابط‌های شبکه در نظر بگیرید:

interface NetworkListener {
    void onAvailable(Network network);
    void onLost(Network network);
    void onChanged(Network network);
}

هنگام توقف موقت اعلان‌ها به یک برنامه، باید مجموعه شبکه‌ها و وضعیت‌هایی را که برنامه آخرین بار مشاهده کرده است به خاطر بسپارید. پس از از سرگیری، توصیه می‌شود برنامه‌هایی را که از شبکه‌های قدیمی حذف شده‌اند، شبکه‌های جدیدی که در دسترس قرار گرفته‌اند و شبکه‌های موجودی که وضعیت آنها تغییر کرده است، به ترتیب زیر مطلع کنید.

به برنامه در مورد شبکه‌هایی که در دسترس قرار گرفته‌اند و سپس در حین توقف فراخوانی‌های برگشتی از بین رفته‌اند، اطلاع ندهید. برنامه‌ها نباید گزارش کاملی از رویدادهایی که در حین توقف آنها رخ داده است، دریافت کنند و مستندات API نباید قول ارائه جریان‌های رویداد بدون وقفه خارج از حالت‌های چرخه حیات صریح را بدهند. در این مثال، اگر برنامه نیاز به نظارت مداوم بر در دسترس بودن شبکه داشته باشد، باید در حالت چرخه حیات باقی بماند که از ذخیره شدن یا توقف آن جلوگیری کند.

در بررسی، شما باید رویدادهایی را که پس از مکث و قبل از از سرگیری اعلان‌ها اتفاق افتاده‌اند، با هم ترکیب کنید و آخرین وضعیت را به طور خلاصه به callbackهای ثبت‌شده برنامه ارسال کنید.

ملاحظات مربوط به مستندات توسعه‌دهندگان

تحویل رویدادهای ناهمزمان ممکن است به تأخیر بیفتد، یا به این دلیل که فرستنده همانطور که در بخش قبل نشان داده شد، تحویل را برای مدت زمانی متوقف کرده است یا به این دلیل که برنامه گیرنده منابع دستگاه کافی برای پردازش به موقع رویداد را دریافت نکرده است.

توسعه‌دهندگان را از حدس و گمان در مورد زمان بین اطلاع‌رسانی به برنامه‌شان از یک رویداد و زمان وقوع آن رویداد منصرف کنید.

انتظارات توسعه‌دهندگان برای تعلیق APIها

توسعه‌دهندگانی که با همزمانی ساختاریافته کاتلین آشنا هستند، رفتارهای زیر را از هر API در حال تعلیق انتظار دارند:

توابع Suspend باید قبل از بازگشت یا throw کردن، تمام کارهای مرتبط را انجام دهند.

نتایج عملیات غیر مسدودکننده به عنوان مقادیر بازگشتی تابع عادی بازگردانده می‌شوند و خطاها با ارسال استثنائات گزارش می‌شوند. (این اغلب به این معنی است که پارامترهای فراخوانی غیر ضروری هستند.)

توابع Suspend فقط باید پارامترهای callback را درجا فراخوانی کنند.

توابع Suspend همیشه باید قبل از بازگشت، تمام کارهای مرتبط را تکمیل کنند، بنابراین هرگز نباید یک تابع callback یا پارامتر تابع دیگر ارائه شده را فراخوانی کنند یا پس از بازگشت تابع suspend، ارجاعی به آن را حفظ کنند.

توابعی که پارامترهای فراخوانی را می‌پذیرند باید با حفظ متن به حالت تعلیق درآیند، مگر اینکه خلاف آن مستند شده باشد.

فراخوانی یک تابع در یک تابع suspend باعث می‌شود که آن تابع در CoroutineContext مربوط به فراخوانی‌کننده اجرا شود. از آنجایی که توابع suspend باید تمام کارهای مرتبط را قبل از return یا throw کردن تکمیل کنند و فقط باید پارامترهای callback را درجا فراخوانی کنند، انتظار پیش‌فرض این است که هرگونه callback از این دست نیز روی CoroutineContext فراخوانی‌کننده با استفاده از dispatcher مرتبط با آن اجرا شود. اگر هدف API اجرای یک callback خارج از CoroutineContext فراخوانی‌کننده باشد، این رفتار باید به وضوح مستند شود.

توابع Suspend باید از لغو کار kotlinx.coroutines پشتیبانی کنند

هر تابع suspend ارائه شده باید با لغو job مطابق تعریف kotlinx.coroutines همکاری کند. اگر job فراخوانی یک عملیات در حال انجام لغو شود، تابع باید در اسرع وقت با یک CancellationException از سر گرفته شود تا فراخواننده بتواند در اسرع وقت آن را پاک‌سازی کرده و ادامه دهد. این کار به طور خودکار توسط suspendCancellableCoroutine و سایر APIهای suspending ارائه شده توسط kotlinx.coroutines انجام می‌شود. پیاده‌سازی‌های کتابخانه‌ای معمولاً نباید مستقیماً suspendCoroutine استفاده کنند، زیرا به طور پیش‌فرض از این رفتار لغو پشتیبانی نمی‌کند.

توابعی که کار مسدودسازی را روی یک پس‌زمینه (نخ غیر اصلی یا رابط کاربری) انجام می‌دهند، باید راهی برای پیکربندی توزیع‌کننده‌ی مورد استفاده ارائه دهند.

توصیه نمی‌شود که یک تابع مسدودکننده را برای تغییر نخ‌ها به طور کامل به حالت تعلیق درآورید.

فراخوانی یک تابع suspend نباید منجر به ایجاد threadهای اضافی شود، مگر اینکه به توسعه‌دهنده اجازه دهد thread یا thread pool خود را برای انجام آن کار فراهم کند. برای مثال، یک سازنده ممکن است یک CoroutineContext بپذیرد که برای انجام کار پس‌زمینه برای متدهای کلاس استفاده می‌شود.

توابعی که فقط یک پارامتر اختیاری CoroutineContext یا Dispatcher برای سوئیچ به آن dispatcher جهت انجام کار مسدودسازی می‌پذیرند، باید به حالت تعلیق درآیند و در عوض، تابع مسدودسازی زیرین در معرض دید قرار گیرد و به توسعه‌دهندگان فراخواننده توصیه می‌شود که از فراخوانی خود به withContext برای هدایت کار به یک dispatcher انتخاب‌شده استفاده کنند.

کلاس‌هایی که کوروتین‌ها را راه‌اندازی می‌کنند

کلاس‌هایی که کوروتین‌ها را راه‌اندازی می‌کنند، باید یک 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 برای انجام کار همزمان، به فراخوانی‌کننده اجازه می‌دهد تا عملیات را در زمینه‌ی خود فراخوانی کند و نیاز به مدیریت یک CoroutineScope MyClass را از بین می‌برد. سریال‌سازی پردازش درخواست‌ها ساده‌تر می‌شود و state اغلب می‌تواند به عنوان متغیرهای محلی handleRequests وجود داشته باشد، نه به عنوان ویژگی‌های کلاس که در غیر این صورت نیاز به همگام‌سازی اضافی دارند.

کلاس‌هایی که کوروتین‌ها را مدیریت می‌کنند باید متدهای close و cancel را نمایش دهند.

کلاس‌هایی که کوروتین‌ها را به عنوان جزئیات پیاده‌سازی راه‌اندازی می‌کنند، باید راهی برای خاموش کردن کامل آن وظایف همزمان در حال انجام ارائه دهند تا کار همزمان کنترل نشده به محدوده والد نشت نکند. معمولاً این به شکل ایجاد یک Job فرزند از یک CoroutineContext ارائه شده است:

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() ، هیچ عملیات جدیدی نمی‌تواند آغاز شود.

سازنده‌های کلاس CoroutineContext را می‌پذیرند، نه CoroutineScope را.

وقتی اشیاء از پرتاب مستقیم به یک دامنه والد مشخص منع می‌شوند، مناسب بودن 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 در یک سطح API ظاهر می‌شود، مقدار پیش‌فرض باید Empty`CoroutineContext` باشد. این امر امکان ترکیب بهتر رفتارهای API را فراهم می‌کند، زیرا با مقدار 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)

    // ...
}