Coroutine در اندروید (قسمت اول)

Coroutine در اندروید  (قسمت اول)
فهرست مقاله [نمایش]

    این بخشی از یک مقاله چند قسمتی در مورد استفاده از Coroutine در اندروید است. در این اینجا به نحوه کارکرد Coroutine ها و این که چه مشکلی حل می شود بحث میکنیم.

    برای مطالعه قسمت دوم اینجا  کلیک کنید.

    برای مطالعه قسمت سوم اینجا  کلیک کنید.

    Coroutine ها چه مشکلی را حل می کنند؟

    Coroutine های کاتلین سبک جدیدی از همزمانی را معرفی می کنند که می تواند در Android برای ساده کردن کد async مورد استفاده قرار گیرد. در حالی که آنها در کاتلین در ورژن 1.3 هستند ، مفهوم Coroutine ها از طلوع زبان های برنامه نویسی وجود داشته است. اولین زبانی که از Coroutine ها استفاده کرد Simula در سال 1967 بود.

    در چند سال گذشته ، Coroutine ها محبوبیت زیادی پیدا کرده اند و اکنون در بسیاری از زبان های برنامه نویسی محبوب مانند Javascript ، C# ، پایتون ، روبی و… گنجانده شده اند تا نام های خود را معرفی کنند. Coroutine های Kotlin بر پایه مفاهیم مستقر است که برای ساختن برنامه های بزرگ استفاده شده اند.  

    در Android ، coroutine یک راه حل عالی برای دو مشکل است:

    1. کارهای طولانی مدت کارهایی هستند که خیلی طول می کشد تا thread اصلی مسدود شود.
    2. 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 ، می توانید از آنها برای حل دو مشکل واقعاً معمول استفاده کنید:

    1. ساده کردن کد برای کارهای طولانی مدت مانند خواندن از شبکه ، دیسک یا حتی تجزیه نتایج JSON بزرگ.
    2. انجام  main-safety دقیق برای اطمینان از این که هرگز به طور تصادفی  main thread را بدون ایجاد کد های سخت مسدود نمی کنید.

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


    • نویسنده: میثم بابائی

    ارسال دیدگاه

    برای افزودن دیدگاه خود، نیاز است ابتدا وارد حساب کاربری‌تان شوید


    دیدگاه کاربران