اگر مقاله قبلی رو در مورد coroutines نخوندید حتما اول بخونیدش و بعد این مقاله رو مطالعه کنید ، این مقاله شبیه مقاله قبلی خواهد بود اما در مقاله قبلی ما نگاه کلی تری داشتیم و اینجا سعی میکنیم جزئی تر به این قضیه نگاه کنیم
در این مقاله مفاهمی که شما در مورد coroutine باید بدونید رو بررسی میکنیم :
چندین حالت برای Scope وجود داره ، Scope در لغت به معنی حوزه و ... میشه ، پس میتونیم در coroutines بگیم که coroutines هایی که شما تعریف میکنید باید Scope بندی بشن به فارسی یعنی طبقه بندی بشن ، Scope های مختلف به این شکل هستن :
یک Scope عمومی که شما باید یک Context بهش پاس بدید ، این Context مشخص کننده و تفکیک کننده Coroutines ها از هم میشه ، سه حالت برای Context وجود داره :
Default
یک مجموعه از Thread ها رو برای اجرای عملیات مشخص میکنه
IO
یک مجموعه از Thread ها رو برای اجرای عملیات مشخص میکنه ، این مجموعه زیرمجموعه Thread های Default خواهد بود و تعدادشون کمتره
Main
مشخص میکنه که عملیات در Main Thread یا UI Thread انجام بشه
به این Context ها Dispatchers میگن (در لغت به معنی اعزام کننده) که مشخص میکنه عملیات ما روی چه Thread هایی انجام بشه
یک Scope که روی Main Thread یا UI Thread انجام میشه ، پس در واقع همون CoroutinesScope ای میشه که Context اون Main باشه
وقتی شما در این Scope کاری انجام بدید یک حالت Global برای اون کار ایجاد میکنه ، یعنی چی ؟ یعنی وابستگی اون رو به چیزای دیگه قطع میکنه ، اگه این Scope رو داخل یک Scope دیگه اجرا کنید و Scope پدر رو لغو کنید ، GlobalScope لغو نمیشه ! پس باید حواستون جمع باشه کجا و چرا ازش استفاده کنید .
کمی در مورد Context ها صحبت کردیم ، فهمیدیم که سه حالت انتخاب میتونیم داشته باشیم (البته حالت چهارمی هم به نام Unconfined وجود داره ، این Scope میاد و در همون Thread ای که از قبل عملیات داشته اجرا میشده اجرا میشه و یک سری چیزای دیگه ، خیلی مهم نیست برای همین در طبقه بندی نیاوردم) اما Context ها فقط محدود به این چند مورد نیست ، ما میتونیم اِلمانهای دیگه ای رو هم دخیل کنیم :
یکی از مواردی که میشه دخیل کرد Exception ها هستن :
نکته 1 : اپراتوری که ملاحظه میکنید یعنی :؟ اسمش هست اپراتور الویس (چون شبیه موهای الویس پریسلیه) این اپراتور اگه مقدار throwable.message نال باشه به جاش مقداری که بعد از اپراتور اومده رو به عنوان خروجی میگیره (که اینجا کلمه "null" بوده) ، من بعدها در مورد اپراتورهای کاتلین مقاله ای رو در ویرگول و Medium میذارم (راستی شما میتونید مقاله منو در مورد Dagger در Medium بخونید : بخش اول ، بخش دوم)
البته شما میتونید به جای اینکار طبق چیزی که در مقاله قبلی هم گفتم از invokeOnCompletion استفاده کنید
پارامتر دیگهای که میتونه به عنوان Context دخیل باشه Job هست ، طبق تعریف سایت مرجع :
A background job. Conceptually, a job is a cancellable thing with a life-cycle that culminates in its completion.
یک کارِ پس زمینه : یک کار یک چیزِ (!) قابل لغو است که برای خود چرخه حیات دارد و در نهایت پایان مییابد
در واقع شما با دستور launch که در مثال های بالا ملاحظه کردید یک Job رو فعال میکنید ، چرخه حیات Job رو میتونید در این شکل ببینید :
شما میتونید در ورودی CoroutineScope به عنوان پارامتر Job رو هم دخیل کنید تا بعدا بتونید کنترل چرخه حیات اون رو به دست بگیرید (مثلا لغوش کنید) :
یک ویژگی Job ها اینه که میتونن رابطه والد و فرزند داشته باشن ، برای مثال :
اگر شما job ای رو لغو کنید یا اون Job به exception ای بر بخوره، کلیه job های فرزند اون لغو خواهند شد :
نکته : شما نباید Scope رو تغییر بدید ، اگه Scope ای که Job فرزند درونش هست تغییر پیدا کنه پروسه لغو روی اجرا نمیشه
برای لغو یک جاب میتونید اون رو cancel کنید :
یک job رو میتونید با مقادیر زیر کنترل کنید و حالت های مختلفش رو در مواقعی که فعال/کامل/لغو شده باشه در کدتون بررسی کنید :
تا الان دیدیم که هر وقت نیازی به اجرای یک Job بود با دستور launch اونو اجرا میکردیم ، در واقع launch یک Builder هست ، ما دو نوع Builder و شروع کننده داریم :
به حالت fire and forget یک coroutine جدید را شروع میکنه ، یعنی نتیجه و result به محض آماده شدن برگردونده میشه (البته میشه همچنان لغو یا ... اش کرد)
به شما این اجازه رو میده که coroutine رو ایجاد کنید ولی نتیجه رو در یک تابع دیگه به اسم await دریافت میکنه ، به مثال زیر دقت کنید :
در کد بالا ما مقدار 1 و 2 رو در دو async برگردوندیدم ، متغیر result بعد از 6 ثانیه عدد 3 خواهد بود ، فکر کنم کاربرد رو متوجه شده باشید ؟ بعضی مواقع شما نیاز چند پارامتر رو باید محاسبه کنید یا چند کار مختلف باید انجام بشن و نتیجه ترکیبی از این چند کار خواهد بود ، با async و await میتونید این کار رو انجام بدید .
فرق تابع عادی با تابعی که شما اولش کلمه کلیدی suspend رو میگذارید چیه ؟ منظور از suspend در اینجا اینه که شما میتونی اون عملیات رو برای لحظه ای به وقفه بندازید و دوباره شروعش کنید ، در عکسی که بالاتر دیدید ، تابع stuff1 و stuff2 هر دو از جنس suspend بودند .
نکته : شما یک تابع suspend رو فقط میتونید از طریق یک تابع suspend دیگه یا از طریق coroutine صدا بزنید و نمیتونید در حالت عادی فراخونیش کنید
این عبارت رو در خیلی از مقالات خارجی میتونید ببینید و با خودتون بگید قضیه چیه ؟ قبل از اون یک مثال ببینیم :
ما در Main Scope دو coroutine رو به صورت مجزا اجرا کردیم ، اگه این کد رو اجرا کنید میفهمید که delay موجود در دومی باعث وقفه در اولی نمیشه ، حالا اونو به این کد تغییر میدم :
به نظر شما خروجی چیه ؟ آیا سه تابع stuff1 پشت هم چاپ میشن و delay دومی مثل مثال قبل باعث وقفه در coroutine اولی نمیشه ؟ نه ! دلیل اون قسمت runBlocking هست ، runBlocking مثل launch یک scope برای coroutine ایجاد میکنه اما یک تفاوت داره ، اگه شما چند تا launch رو با هم اجرا کنید میتونن موازی اجرا بشن ولی اگه از runBlocking استفاده کنید Thread ما Block میشه و تا زمانی که عملیات درون runBlocking تموم نشه سراغ عمیلات های دیگه ای که در اون scope (که در اینجا Main Scope هست) نمیره .
نکته : اگه به جای Main از IO استفاده میکردم Block صورت نمیگرفت ، دلیل اون اینه که در IO عملیات روی چند Thread پخش میشه بنابراین اگه Block ای صورت بگیره Thread های دیگه هستن که عملیات های دیگه رو انجام بدن ولی Main فقط Main Thread یا همون UI Thread رو شامل میشه پس وقتی Block بشه جای دیگه ای برای اجرا عملیات ها نیست
بعد از خوندن مقاله قبلی و این مقاله شما باید با اصول اولیه coroutine آشنا شده باشید ، در مقالات بعدی سعی میکنم نکات خاص و استفاده کاربردی اونو برای شما توضیح بدم .