
این بخشی از یک مقاله چند قسمتی در مورد استفاده ازCoroutineدر اندروید است. در این اینجا به نحوه کارکردCoroutine ها و این که چه مشکلی حل می شود بحث میکنیم.
برای مطالعه قسمت دوم اینجا کلیک کنید.
برای مطالعه قسمت سوم اینجا کلیک کنید.
Coroutine ها چه مشکلی را حل می کنند؟
Coroutine های کاتلین سبک جدیدی از همزمانی را معرفی می کنند که می تواند درAndroidبرای ساده کردن کدasyncمورد استفاده قرار گیرد. در حالی که آنها در کاتلین در ورژن 1.3 هستند ، مفهومCoroutine ها از طلوع زبان های برنامه نویسی وجود داشته است. اولین زبانی که ازCoroutine ها استفاده کردSimulaدر سال 1967 بود.
در چند سال گذشته ،Coroutine ها محبوبیت زیادی پیدا کرده اند و اکنون در بسیاری از زبان های برنامه نویسی محبوب مانندJavascript،C#، پایتون ، روبی و…گنجانده شده اند تا نام های خود را معرفی کنند.Coroutine هایKotlinبر پایه مفاهیم مستقر است که برای ساختن برنامه های بزرگ استفاده شده اند.
درAndroid،coroutineیک راه حل عالی برای دو مشکل است:
- کارهای طولانی مدت کارهایی هستند که خیلی طول می کشد تا thread اصلی مسدود شود.
- Main-safety به شما امکان می دهد اطمینان حاصل کنید که هرگونه متد suspend را می توان از thread اصلی فراخوانی کرد.
کارهای طولانی(Long running tasks)
واکشی صفحه وب یا تعامل باAPIهر دو شامل ایجاد یک درخواست شبکه می شود. به همین ترتیب ، خواندن از یک پایگاه داده یا بارگذاری یک تصویر از دیسک شامل خواندن یک پرونده است. این نوع موارد همان چیزی است که من به آنها وظایف طولانی اجرا می کنم - کارهایی که خیلی طول می کشد تا برنامه شما متوقف شود و منتظر آنها بماند
درک این مسئله که سرعت یک تلفن مدرن در مقایسه با درخواست شبکه سرعت دارد، کار سختی است. در پیکسل 2 ، یک چرخهCPUتنها کمتر از 0.0000000004 ثانیه طول می کشد ، عددی که درک آن از نظر انسانی بسیار سخت است. با این حال ، اگر به درخواست شبکه به عنوان یک چشم به هم زدن، حدود 400 میلی ثانیه (0.4 ثانیه) فکر می کنید ، می توان فهمید که سرعتCPUچگونه کار می کند. در یک چشم بر هم زدن یا درخواست شبکه تا حدی کند ،CPUمی تواند بیش از یک میلیارد چرخه را اجرا کند!
درAndroidهر برنامه دارای یکmain threadاست که وظیفه دستیابی بهUI(مانند ترسیم نمای) و هماهنگی تعامل کاربر را دارد. اگر کارهای زیادی روی اینthreadانجام شود ، به نظر می رسد که این برنامه هنگ کرده یا کُند شود و منجر به یک تجربه کاربری نامطلوب شود. هر کار طولانی در حال اجرا باید بدون مسدود کردنmain threadانجام شود ، بنابراین برنامه شما آنچه را که "jank" نامیده می شود مانند انیمیشن های یخ زده ، نشان نمی دهد یا برای لمس کردن رویدادها به آرامی پاسخ می دهد.
به منظور انجام درخواست شبکه ازmain thread، یک الگوی متداول callbackاست.Callback ها داده ای را به كتابخانه ای ارائه می دهد كه می تواند در برخی از مواقع در آینده برایcall backبا كد شما استفاده كند.
ممکن است مانند این باشد:
class ViewModel: ViewModel() {
fun fetchDocs() {
get("developer.android.com") { result ->
show(result)
}
}
}
اگرچهgetازmain threadفراخوانی شده است اما برای انجام درخواست شبکه ازthreadدیگری استفاده خواهد کرد. سپس ، هنگامی که نتیجه از شبکه در دسترس باشد ، پاسخ بهmain threadارسال می شود. این یک روش عالی برای انجامtaskهای طولانی مدت است و کتابخانه هایی مانندRetrofitمی توانند به شما در درخواست شبکه بدون مسدود کردنmain threadکمک کنند.
استفاده ازcoroutineبرای کارهای طولانی مدت
Coroutine روشی برای ساده کردن کد مورد استفاده برای مدیریت کارهای طولانی مدت مانند دریافت اسناد از سرور است. برای کشف چگونگی ساده تر ساختن کد برای کارهای طولانی مدت ساده ، بگذارید مثالcallbackدر بالا را بازنویسی کنیم تا ازcoroutine استفاده کنیم.
// Dispatchers.Main
suspend fun fetchDocs() {
// Dispatchers.Main
val result = get("developer.android.com")
// Dispatchers.Main
show(result)
}
// look at this in the next section
suspend fun get(url: String) = withContext(Dispatchers.IO) {
/*...*/
}
آیا این کدmain threadرا مسدود نمی کند؟ بدون اینکه منتظر درخواست شبکه و مسدود شدن باشید ، چگونه نتیجه را به دست می آورد؟ به نظر می رسد کهcoroutineراهی را برای اجرای کاتلین فراهم می کند و هرگزmain threadرا مسدود نمی کند.
با استفاده از دو عملیات جدید ،coroutineها بر اساس متد های منظم ساخته می شوند. علاوه برinvoke(یاcall) وreturn،coroutineها به حالتsuspendوresumeاضافه می شوند.
- suspend- مکث اجرای مجدد جریان فعلی ، صرفه جویی در تمام متغیرهای محلی
- resume- از محل مکثcoroutine,suspendرا ادامه دهید
این ویژگی توسط کاتلین با کلید واژهsuspendروی تابع اضافه شده است. شما فقط می توانید توابعsuspendرا از دیگر توابعsuspendفراخوانی کنید یا با استفاده از یکcoroutine builderمانند راه اندازی برای شروع یکcoroutineجدید.
Suspend و resumeبرای جایگزین کردن callback ها
در مثال بالا قبل از اجرای درخواست شبکهgetراsuspend میکنیم. متدgetهنوز مسئولیت اجرای درخواست شبکه را ازmain threadبر عهده خواهد داشت. سپس ، هنگامی که درخواست شبکه کامل شد ، به جای callbackبرای اطلاع ازmain thread، می توانید مجدداًcoroutineکهsuspendاست را از سر بگیرد.
انیمیشن که نشان می دهد چگونه کاتلینsuspendو ازresumeرا برای جایگزین کردنcallbackها نشان می دهد.
با نگاهی به نحوه اجرایfetchDocsمی توانیدsuspendرا مشاهده کنید. هرگاه یکcoroutine, suspendمیشود،current stack frame(مکانی که کاتلین برای ردیابی متد در حال اجرا و متغیرهای آن استفاده می کند) کپی شده و ذخیره می شود.هنگامی که از سر می گیرد ،stack frameاز جایی که ذخیره شده است کپی می شود و دوباره شروع به کار می کند. در وسط انیمیشن - هنگامی که تمامcoroutineروی main threadبه صورتsuspendاست ،main threadبرای به روزرسانی صفحه و رسیدگی به اتفاقات کاربر آزاد است.Suspendوresumeجایگزینcallbackها.
وقتی همهcoroutineها روی main threadبه حالت suspendباشند ،main threadبرای انجام سایر کارها آزاد است.
حتی اگر ما کدی را نوشتیم که برای درخواست مسدود کردن شبکه است ، اماcoroutineها کد ما را دقیقاً همانطور که می خواهیم اجرا می کنند و از مسدود کردنmain threadجلوگیری می کنند!
در مرحله بعدی ، بیایید به نحوه استفاده ازcoroutineبرایmain-safetyوexplore dispatchersبپردازیم.
Main-safety با coroutine
درcoroutineهای کاتلین ، توابعsuspendبرای فراخوانی همیشه ازmain threadاستفاده میکنند مهم نیست که چه کاری انجام میدهند آنها باید اجازه دهند که هرthreadبتواند آنها را فراخوانی کند.
اما ، بسیاری از کارهایی که در برنامه هایAndroidانجام می دهیم بسیار کند است که درmain threadاتفاق میفتد. درخواست های شبکه ، تجزیهJSON، خواندن یا نوشتن از پایگاه داده ، یا حتی فقط تکرار لیست های بزرگ. هر یک از این قابلیت ها را دارد که به اندازه کافی کند عمل کند و باعث شود کاربر از "jank" استفاده کند و بایدmain threadرا خاموش کند.
استفاده از suspendبه کاتلین نمی گوید که یک تابع را در یکbackground threadاجرا کند. شایان ذکر است که به طور واضح و مکرر می گویند کهcoroutineها رویmain threadا جرا می شوند. در واقع ، این ایده خوبی است که ازDispatchers.Main.immediateهنگام راه اندازی یکcoroutineدر پاسخ به یک رویدادUIاستفاده کنید از این طریق ، اگر شما به عنوان یک کار طولانی مدت که نیاز به main-safetyداشته باشد ، به پایان نرسیدید. درframeبعدی برای کاربر در دسترس باشد.
Coroutine رویmain threadاجرا می شود ، و suspendبه معنای پس زمینه نیست.
برای ایجاد تابعی که کار را درmain threadخیلی آهسته انجام دهد ، می توانید به coroutineبگویید که کار را روی DefaultیاIO Disatcherانجام دهد. در کاتلین ، تمام coroutineها باید در یکdispatcherاجرا شوند - حتی اگر آنهاmain threadکار کنند. coroutineها می توانند خود را به حالت suspendدرآورند ، وdispatcherچیزی است که می داند چگونه آنها راresumeکند.
برای مشخص کردن محل اجرایcoroutineهای کاتلین سه Dispatcherرا می توانید برایthread dispatchفراهم کنید.
+-----------------------------------+
| Dispatchers.Main |
+-----------------------------------+
| Main thread on Android, interact |
| with the UI and perform light |
| work |
+-----------------------------------+
| - Calling suspend functions |
| - Call UI functions |
| - Updating LiveData |
+-----------------------------------+
+-----------------------------------+
| Dispatchers.IO |
+-----------------------------------+
| Optimized for disk and network IO |
| off the main thread |
+-----------------------------------+
| - Database* |
| - Reading/writing files |
| - Networking** |
+-----------------------------------+
+-----------------------------------+
| Dispatchers.Default |
+-----------------------------------+
| Optimized for CPU intensive work |
| off the main thread |
+-----------------------------------+
| - Sorting a list |
| - Parsing JSON |
| - DiffUtils |
+-
- در صورت استفاده از توابعsuspend،RxJavaیاLiveData،Roomبه طور خودکارmain-safetyرا تأمین می کند.
- كتابخانه هاي شبكه مانندRetrofitوVolley,threadهای مربوط به خود را مديريت مي كنند و در استفاده ازcoroutineها کاتیلن نياز بهmain-safetyدر كد ندارند.
برای ادامه با مثال بالا ، بیایید ازDisatcherها برای تعریف متدgetاستفاده کنیم. در داخل بدنه withContext(Dispatchers.IO)را می گیرید تا یک بلوک ایجاد کنید که رویIO dispatcherکار کند. هر کدی که داخل آن بلوک قرار دهید ، همیشه درIO dispatcherاجرا خواهد شد. از آنجا کهwithContextبه خودی خود یک تابع suspendاست ، با استفاده ازcoroutineبرای تأمینmain safetyکار خواهد کرد.
// Dispatchers.Main
suspend fun fetchDocs() {
// Dispatchers.Main
val result = get("developer.android.com")
// Dispatchers.Main
show(result)
}
// Dispatchers.Main
suspend fun get(url: String) =
// Dispatchers.Main
withContext(Dispatchers.IO) {
// Dispatchers.IO
/* perform blocking network IO here */
}
// Dispatchers.Main
با استفاده ازcoroutineها می توانید با کنترل ریز دانه ایthread dispatchرا انجام دهید. از آنجا کهwithContextبه شما امکان می دهد بدون ایجاد پاسخ بهcallbackبرای بازگشت نتیجه ، هر خط کد را اجرا کنید ، می توانید کنترل کنید ، می توانید آن را در توابع بسیار کوچک مانند خواندن از بانک اطلاعاتی خود یا انجام درخواست شبکه اعمال کنید. بنابراین یک کار خوب این است که با استفاده ازContextاطمینان حاصل کنید که هر متدی به درستی کارمیکند تا در مورد هرDispatcherاز جملهMainفراخوانی شود - به این ترتیب تماس گیرنده هرگز نباید در مورد اینکه چه چیزی برای اجرای متد مورد نیاز خواهد بود فکر کند. به این ترتیب متدی که این ها را فراخوانی میکند هرگز نباید در باره threadمورد نیاز برای اجرای متد فکر کند.
در این مثال ،fetchDocsدر حال اجرای main threadاست ، اما با اطمینان می توانید ازgetکه یک درخواست شبکه را در backgroundانجام می دهد استفاده کنید. از آنجا کهcoroutineها از حالتsuspendوresumeپشتیبانی می کنند ، به محض کامل شدن بلوکwithContext،coroutineرویmain threadبا نتیجه از سر گرفته می شود.
توابعsuspendاگربه خوبی نوشته شوند همیشه برای اجرا از main thread(یاmain-safe) میتوانند استفاده کنند.
این ایده خوبی است که هر متدsuspendرا بهmain-safeتبدیل کنید. اگر هر کاری را انجام داد که دیسک ، شبکه یا حتی فقط ازCPUخیلی زیاد استفاده کند ، با استفاده ازConxon برای ارتباط باthreadاز main threadاستفاده می کند این الگویی است که کتابخانه های مبتنی بر coroutineمانندRetrofitوRoomاز آن پیروی می کنند اگر این سبک را در سراسرcodebaseخود اجرا کنید ، کد شما بسیار ساده تر خواهد بود و از تداخل ارتباطthreadبا منطق برنامه جلوگیری می کنید. با این کار ،coroutineها آزاد هستند تا با استفاده از کد ساده ، روی main threadراه اندازی شوند و درخواست های شبکه یا بانک اطلاعاتی را با کد ساده انجام دهند در حالی که تضمین می کنند کاربران "jank" را نمی بینند.
عملکردwithContext
withContextبرای تأمین main safetyبه همان سرعت برایcallbackهای برگشتی یاRxJavaاست. حتی می توان فراخوانی هایContextرا فراتر از آنچه ممکن است باcallbackهای برگشتی در برخی شرایط ممکن باشد ، بهینه کرد.
اگر یک تابع 10 بار با یک پایگاه داده ارتباط برقرار کند ، می توانید به کاتلین بگویید که در هر 10 بار با یک بار ارتباط خارجیwithContextجابجا شود. سپس ، حتی اگر کتابخانه بانک اطلاعاتی به طور مکرر باContextارتباط برقرار کند ، در همانdispatcherباقی می ماند و یک مسیر سریع را دنبال می کند. علاوه بر این ، جابجایی بینDispatchers.DefaultوDispatchers.IOبهینه شده است تا در هر زمان ممکن از سوئیچ های threadاستفاده کنید.
در قسمت بعدی چه چیزی را آموزش میدهیم؟
در این قسمت ما بررسی کردیم که coroutineها برای حل چه مشکلاتی هستند. Coroutine یک مفهوم واقعاً قدیمی در زبانهای برنامه نویسی است که اخیراً به دلیل توانایی آنها در ساختن کدهایی که با شبکه ارتباط برقرار می کنند محبوبیت زیادی پیدا کرده است.
درAndroid، می توانید از آنها برای حل دو مشکل واقعاً معمول استفاده کنید:
- ساده کردن کد برای کارهای طولانی مدت مانند خواندن از شبکه ، دیسک یا حتی تجزیه نتایجJSONبزرگ.
- انجام main-safetyدقیق برای اطمینان از این که هرگز به طور تصادفی main threadرا بدون ایجاد کد های سخت مسدود نمی کنید.
درمقاله بعدی ما چگونگی عملکرد آنها را درAndroidبررسی خواهیم کرد تا همه کارهایی را که از یک صفحه شروع کرده اید ردیابی کنید.
جدیدترین ویدئوهای آموزشی
در بخش TV باگتو، آموزش های کوتاه و جدید را مشاهده نمایید