APIهای غیرمسدود کننده درخواست می کنند تا کار اتفاق بیفتد و سپس کنترل را به رشته فراخوانی بازگرداند تا بتواند قبل از اتمام عملیات درخواستی، کارهای دیگری را انجام دهد. این APIها برای مواردی مفید هستند که کار درخواستی ممکن است در حال انجام باشد یا ممکن است نیاز به انتظار برای تکمیل I/O یا IPC، در دسترس بودن منابع سیستم بسیار مورد بحث، یا ورودی کاربر قبل از ادامه کار باشد. به خصوص APIهایی که به خوبی طراحی شده اند، راهی برای لغو عملیات در حال انجام و توقف انجام کار از طرف تماس گیرنده اصلی، حفظ سلامت سیستم و عمر باتری در زمانی که دیگر نیازی به عملیات نیست، فراهم می کنند.
API های ناهمزمان یکی از راه های دستیابی به رفتار غیرانسدادی هستند. APIهای Async نوعی از ادامه یا پاسخ به تماس را می پذیرند که پس از تکمیل عملیات یا سایر رویدادها در طول پیشرفت عملیات مطلع می شود.
دو انگیزه اصلی برای نوشتن یک API ناهمزمان وجود دارد:
- اجرای چندین عملیات به صورت همزمان، که در آن عملیات N باید قبل از تکمیل عملیات N-1 آغاز شود.
- اجتناب از مسدود کردن یک رشته تماس تا زمانی که یک عملیات کامل شود.
کاتلین به شدت همزمانی ساختاریافته را ترویج میکند، مجموعهای از اصول و APIهای ساخته شده بر روی توابع تعلیق که اجرای همزمان و ناهمزمان کد را از رفتار مسدودکننده رشته جدا میکند. توابع تعلیق غیر مسدود کننده و همزمان هستند.
توابع تعلیق:
- رشته فراخوانی آنها را مسدود نکنید و به جای آن رشته اجرای آنها را به عنوان جزئیات پیاده سازی در حالی که منتظر نتایج عملیات اجرا شده در جای دیگر هستید، ارائه دهید.
- به صورت همزمان اجرا کنید و از تماس گیرنده یک API غیرمسدود کننده نیاز ندارید که همزمان با کار غیرانسدادی که توسط تماس API آغاز شده است، اجرا را ادامه دهد.
این صفحه حداقل انتظاراتی را که توسعهدهندگان میتوانند با خیال راحت در هنگام کار با APIهای غیرمسدود و ناهمزمان داشته باشند، شرح میدهد، و به دنبال آن مجموعهای از دستور العملها برای نوشتن APIهایی که این انتظارات را در Kotlin یا به زبانهای جاوا، در پلتفرم Android یا کتابخانههای Jetpack برآورده میکنند، ارائه میکند. در صورت شک، انتظارات توسعه دهنده را به عنوان الزامات هر سطح API جدید در نظر بگیرید.
انتظارات توسعه دهندگان برای API های غیر همگام
انتظارات زیر از نقطه نظر APIهای غیر معلق نوشته شده است، مگر اینکه خلاف آن ذکر شده باشد.
APIهایی که پاسخ تماس را می پذیرند معمولاً ناهمزمان هستند
اگر یک API پاسخی را بپذیرد که مستند نشده است که فقط در محل فراخوانی شود، (یعنی فقط توسط رشته فراخوانی قبل از بازگشت خود فراخوانی API فراخوانی شود)، API ناهمزمان فرض میشود و API باید تمام انتظارات دیگر مستند شده در بخشهای زیر را برآورده کند.
نمونهای از تماس برگشتی که فقط در محل نامیده میشود، یک نقشه یا تابع فیلتر با مرتبه بالاتر است که قبل از بازگشت، یک نگاشت یا محمول را بر روی هر آیتم در یک مجموعه فراخوانی میکند.
API های ناهمزمان باید در سریع ترین زمان ممکن بازگردند
توسعهدهندگان انتظار دارند APIهای همگامسازی غیرانسدادی باشند و پس از شروع درخواست عملیات، به سرعت بازگردند. فراخوانی یک API async در هر زمان باید همیشه ایمن باشد و فراخوانی یک API async هرگز نباید منجر به فریمهای janky یا ANR شود.
بسیاری از عملیاتها و سیگنالهای چرخه حیات را میتوان توسط پلتفرم یا کتابخانههای درخواستی راهاندازی کرد، و انتظار از یک توسعهدهنده برای حفظ دانش جهانی از تمام سایتهای تماس بالقوه برای کد خود، ناپایدار است. به عنوان مثال، زمانی که محتوای برنامه باید برای پر کردن فضای موجود (مانند RecyclerView
) پر شود، میتوان یک Fragment
در یک تراکنش همزمان در پاسخ به View
اندازهگیری و طرحبندی به FragmentManager
اضافه کرد. یک LifecycleObserver
که به فراخوانی چرخه حیات onStart
این قطعه پاسخ میدهد ممکن است به طور منطقی عملیات راهاندازی یکبارهای را در اینجا انجام دهد، و این ممکن است در یک مسیر کد بحرانی برای تولید یک فریم از انیمیشن عاری از jank باشد. یک توسعهدهنده باید همیشه مطمئن باشد که فراخوانی هر API غیر همگام در پاسخ به این نوع تماسهای چرخه حیات، دلیل فریمهای ناهنجار نخواهد بود.
این بدان معناست که کار انجام شده توسط یک API غیر همگام قبل از بازگشت باید بسیار سبک باشد. ایجاد رکوردی از درخواست و فراخوان مربوطه و ثبت آن با موتور اجرایی که حداکثر کار را انجام می دهد. اگر ثبت نام برای یک عملیات ناهمگام به IPC نیاز دارد، اجرای API باید هر اقدامی را که برای برآورده کردن این انتظار توسعهدهنده لازم است انجام دهد. این ممکن است شامل یک یا چند مورد از موارد زیر باشد:
- پیاده سازی IPC زیربنایی به عنوان یک تماس بایندر یک طرفه
- برقراری تماس دو طرفه بایندر به سرور سیستم در جایی که تکمیل ثبت نیازی به گرفتن قفل شدید ندارد
- ارسال درخواست به یک تاپیک کارگر در فرآیند برنامه برای انجام ثبت مسدود کردن از طریق IPC
API های ناهمزمان باید void برگردانند و فقط برای آرگومان های نامعتبر پرتاب شوند
APIهای Async باید همه نتایج عملیات درخواستی را به پاسخ تماس ارائه شده گزارش دهند. این به توسعه دهنده اجازه می دهد تا یک مسیر کد واحد را برای موفقیت و مدیریت خطا پیاده سازی کند.
API های Async ممکن است آرگومان ها را برای null و پرتاب NullPointerException
بررسی کنند، یا بررسی کنند که آرگومان های ارائه شده در محدوده معتبری قرار دارند و IllegalArgumentException
پرتاب کنند. به عنوان مثال، برای تابعی که یک float
در محدوده 0
تا 1f
را می پذیرد، تابع ممکن است بررسی کند که پارامتر در این محدوده قرار دارد و اگر خارج از محدوده باشد، IllegalArgumentException
پرتاب کند، یا ممکن است یک String
کوتاه برای انطباق با یک قالب معتبر مانند فقط حروف عددی بررسی شود. (به یاد داشته باشید که سرور سیستم هرگز نباید به فرآیند برنامه اعتماد کند! هر سرویس سیستمی باید این بررسی ها را در خود سرویس سیستم تکرار کند.)
تمام خطاهای دیگر باید به پاسخ تماس ارائه شده گزارش شود. این شامل، اما محدود به موارد زیر نیست:
- شکست پایانی عملیات درخواستی
- استثناهای امنیتی برای عدم مجوز یا مجوزهای مورد نیاز برای تکمیل عملیات
- بیش از حد مجاز برای انجام عملیات
- فرآیند برنامه به اندازه کافی "پیش زمینه" برای انجام عملیات نیست
- اتصال سخت افزار مورد نیاز قطع شده است
- خرابی های شبکه
- تایم اوت ها
- مرگ بایندر یا فرآیند از راه دور در دسترس نیست
APIهای ناهمزمان باید مکانیزم لغو را ارائه دهند
APIهای Async باید راهی را ارائه دهند که به عملیات در حال اجرا نشان دهد که تماس گیرنده دیگر به نتیجه اهمیت نمی دهد. این عملیات لغو باید دو چیز را نشان دهد:
ارجاعات سخت به تماس های ارائه شده توسط تماس گیرنده باید منتشر شود
تماسهای ارائهشده به APIهای ناهمگام ممکن است حاوی ارجاعات سخت به نمودارهای شی بزرگ باشد، و کار مداوم با نگه داشتن یک مرجع سخت به آن پاسخ میتواند از جمعآوری آن نمودارهای شیء جلوگیری کند. با انتشار این ارجاعات برگشت به تماس در زمان لغو، این نمودارهای شی ممکن است خیلی زودتر از زمانی که اجازه داده می شد کار تا تکمیل شود واجد شرایط جمع آوری زباله شوند.
موتور اجرایی که کار را برای تماس گیرنده انجام می دهد ممکن است آن کار را متوقف کند
کاری که توسط فراخوانی های API async آغاز می شود ممکن است هزینه بالایی در مصرف برق یا سایر منابع سیستم داشته باشد. APIهایی که به تماسگیرندگان اجازه میدهند وقتی دیگر به این کار نیازی نیست سیگنال دهند، اجازه میدهند کار قبل از مصرف بیشتر منابع سیستم متوقف شود.
ملاحظات ویژه برای برنامه های ذخیره شده یا ثابت شده
هنگام طراحی APIهای ناهمزمان که در آن فراخوان ها از یک فرآیند سیستمی منشأ می گیرند و به برنامه ها تحویل می شوند، موارد زیر را در نظر بگیرید:
- فرآیندها و چرخه عمر برنامه : ممکن است فرآیند برنامه گیرنده در حالت حافظه پنهان باشد.
- فریزر برنامههای ذخیرهشده در حافظه پنهان : ممکن است فرآیند برنامه گیرنده ثابت شده باشد.
هنگامی که یک فرآیند برنامه وارد حالت حافظه پنهان می شود، به این معنی است که به طور فعال هیچ مؤلفه قابل مشاهده برای کاربر مانند فعالیت ها و خدمات را میزبانی نمی کند. برنامه در صورت مشاهده مجدد توسط کاربر در حافظه نگهداری می شود، اما در این بین نباید کار کند. در بیشتر موارد، زمانی که برنامه وارد حالت حافظه پنهان میشود، باید ارسال تماسهای برنامه را متوقف کنید و زمانی که برنامه از حالت حافظه پنهان خارج میشود، آن را از سر بگیرید تا در فرآیندهای برنامههای حافظه پنهان کار ایجاد نکنید.
یک برنامه ذخیره شده در حافظه پنهان نیز ممکن است مسدود شده باشد. هنگامی که یک برنامه ثابت است، زمان CPU صفر را دریافت می کند و به هیچ وجه قادر به انجام هیچ کاری نیست. هر تماسی با تماسهای ثبتشده آن برنامه بافر میشود و زمانی که برنامه مسدود میشود، تحویل داده میشود.
تراکنشهای بافر شده به تماسهای برنامهها ممکن است تا زمانی که برنامه از حالت فریز خارج شود و آنها را پردازش کند، کهنه شود. بافر محدود است و در صورت سرریز شدن باعث از کار افتادن برنامه گیرنده می شود. برای جلوگیری از غلبه بر برنامهها با رویدادهای قدیمی یا سرریز شدن بافرهای آنها، زمانی که فرآیند آنها ثابت است، تماسهای برنامه را ارسال نکنید.
در بررسی:
- باید زمانی که فرآیند برنامه در حافظه پنهان است، ارسال تماسهای برنامه را متوقف کنید .
- در حالی که فرآیند برنامه ثابت است، باید ارسال تماسهای برنامه را متوقف کنید.
ردیابی ایالتی
برای ردیابی زمان ورود یا خروج برنامهها از حالت حافظه پنهان:
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()
تنها در صورتی فراخوانی می شود که فرآیند فریز نشده باشد.
برنامهها اغلب بهروزرسانیهایی را که با استفاده از تماسهای برگشتی دریافت کردهاند، بهعنوان یک عکس فوری از آخرین وضعیت ذخیره میکنند. برای نظارت بر درصد باتری باقیمانده، یک 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 نباید قول ارائه جریانهای رویداد را بدون وقفه خارج از حالتهای چرخه حیات صریح بدهد. در این مثال، اگر برنامه نیاز به نظارت مستمر در دسترس بودن شبکه داشته باشد، باید در یک حالت چرخه حیات باقی بماند که از ذخیره شدن در حافظه پنهان یا ثابت نگه داشته شود.
در بررسی، باید رویدادهایی را که پس از توقف و قبل از ازسرگیری اعلانها رخ دادهاند، با هم ترکیب کنید و آخرین وضعیت را به طور خلاصه به تماسهای برنامه ثبتشده تحویل دهید.
ملاحظات مربوط به مستندات توسعه دهنده
ممکن است تحویل رویدادهای ناهمگام به تعویق بیفتد، یا به این دلیل که فرستنده تحویل را برای مدتی متوقف کرده است، همانطور که در بخش قبل نشان داده شده است یا به این دلیل که برنامه گیرنده منابع دستگاه کافی برای پردازش به موقع رویداد را دریافت نکرده است.
توسعه دهندگان را از فرضیاتی در مورد زمان بین زمانی که برنامه آنها از یک رویداد مطلع می شود و زمانی که رویداد واقعاً رخ داده است، منع کنید.
انتظارات توسعه دهندگان برای تعلیق API ها
توسعه دهندگان آشنا با همزمانی ساختاریافته کاتلین رفتارهای زیر را از هر API معلق انتظار دارند:
توابع تعلیق باید تمام کارهای مرتبط را قبل از بازگشت یا پرتاب کامل کنند
نتایج عملیات غیر انسدادی به عنوان مقادیر بازگشتی تابع عادی برگردانده می شود و خطاها با پرتاب استثنا گزارش می شوند. (این اغلب به این معنی است که پارامترهای برگشت تماس غیر ضروری هستند.)
توابع تعلیق فقط باید پارامترهای پاسخ به تماس را در جای خود فراخوانی کنند
توابع تعلیق همیشه باید تمام کارهای مرتبط را قبل از بازگشت کامل کنند، بنابراین هرگز نباید یک فراخوان ارائه شده یا پارامتر تابع دیگر را فراخوانی کنند یا پس از بازگشت تابع تعلیق، ارجاعی به آن حفظ کنند.
توابع تعلیق که پارامترهای پاسخ به تماس را می پذیرند باید دارای زمینه حفظ شوند، مگر اینکه در غیر این صورت مستند شده باشد
فراخوانی یک تابع در یک تابع تعلیق باعث می شود که در CoroutineContext
تماس گیرنده اجرا شود. از آنجایی که توابع تعلیق باید تمام کارهای مرتبط را قبل از بازگشت یا پرتاب انجام دهند، و فقط باید پارامترهای پاسخ به تماس را در جای خود فراخوانی کنند، انتظار پیشفرض این است که چنین تماسهایی با استفاده از توزیعکننده مرتبط آن در CoroutineContext
فراخوانی نیز اجرا شوند. اگر هدف API اجرای یک فراخوان خارج از فراخوانی CoroutineContext
باشد، این رفتار باید به وضوح مستند شود.
توابع تعلیق باید از لغو کار kotlinx.coroutines پشتیبانی کند
هر تابع تعلیق ارائه شده باید با لغو کار مطابق تعریف kotlinx.coroutines
همکاری کند. اگر کار فراخوانی یک عملیات در حال انجام لغو شود، عملکرد باید با یک CancellationException
در اسرع وقت از سر گرفته شود تا تماسگیرنده بتواند در اسرع وقت پاکسازی و ادامه دهد. این به طور خودکار توسط suspendCancellableCoroutine
و سایر APIهای تعلیق ارائه شده توسط kotlinx.coroutines
مدیریت می شود. پیادهسازی کتابخانه معمولاً نباید مستقیماً از suspendCoroutine
استفاده کند، زیرا به طور پیشفرض از این رفتار لغو پشتیبانی نمیکند.
توابع تعلیق که کار مسدود کردن را در پسزمینه انجام میدهند (غیر اصلی یا رشته رابط کاربری) باید راهی برای پیکربندی توزیعکننده مورد استفاده ارائه کنند.
توصیه نمی شود که یک تابع مسدود کننده را به طور کامل به حالت تعلیق درآورید تا رشته ها را تغییر دهید.
فراخوانی یک تابع تعلیق نباید منجر به ایجاد رشتههای اضافی شود، بدون اینکه به توسعهدهنده اجازه داده شود تا رشته یا Thread Pool خود را برای انجام آن کار عرضه کند. به عنوان مثال، یک سازنده ممکن است یک 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
برای انجام کار همزمان به تماسگیرنده اجازه میدهد تا عملیات را در زمینه خود فراخوانی کند و نیاز به مدیریت یک CoroutineScope
MyClass
حذف کند. سریالسازی پردازش درخواستها سادهتر میشود و حالت اغلب میتواند بهعنوان متغیرهای محلی handleRequests
به جای ویژگیهای کلاس وجود داشته باشد که در غیر این صورت نیاز به همگامسازی اضافی دارند.
کلاسهایی که کوروتینها را مدیریت میکنند باید روشهای بسته و لغو را نشان دهند
کلاسهایی که برنامههای روتین را به عنوان جزئیات پیادهسازی راهاندازی میکنند، باید راهی برای خاموش کردن آن وظایف همزمان مداوم ارائه دهند تا کار همزمان کنترلنشده را در محدوده والد منتشر نکنند. معمولاً این به شکل ایجاد یک 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)
// ...
}