Coroutine در Android (قسمت سوم): کار واقعی

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

    این بخشی از یک مجموعه چند بخشی در مورد استفاده از Coroutine در Android است. این مقاله بر روی حل مشکلات عملی با استفاده از Coroutine با اجرای درخواست های یک باره(one shot) متمرکز است.

    مطالعه قسمت اول

    مطالعه قسمت دوم

    حل مشکلات دنیای واقعی با Coroutine

    بخش یک و دو این مجموعه به چگونگی استفاده از Coroutine برای ساده سازی کد ، ایجاد main-safety در Android و جلوگیری از  leaking work متمرکز شده است. با این پس زمینه ، آنها به عنوان یک راه حل عالی برای پردازش پس زمینه و راهی برای ساده کردن کد مبتنی بر پاسخ در Android به نظر می رسند.

    تا کنون ، ما بر روی آنچه که Coroutine است و نحوه مدیریت آنها تمرکز کرده ایم. در این مقاله نحوه استفاده از آنها برای انجام برخی از کارهای واقعی را بررسی خواهیم کرد. Coroutine یک ویژگی زبان برنامه نویسی برای اهداف عمومی در همان سطح توابع است بنابراین می توانید از آنها برای پیاده سازی هر آنچه که می توانید با توابع و اشیا استفاده کنید. با این وجود ، دو نوع task وجود دارد که تمام وقت با کد واقعی پیش می آیند که Coroutine ها یک راه حل عالی برای آنها هستند:

    1. One shot requests درخواست هایی هستند که با هر بار فراخوانی اجرا می شوند  آنها همیشه پس از آماده شدن نتیجه کامل می شوند.
    2. Streaming requests درخواست هایی هستند که همچنان تغییرات را مشاهده می کنند و آنها را به تماس گیرنده گزارش می دهند - با آماده شدن اولین نتیجه  آنها تکمیل نمی شوند.

    Coroutine یک راه حل عالی برای هر دو این وظایف است. در این مقاله ، ما عمیقا به درخواست های یک باره(shot request) نگاه خواهیم کرد و نحوه پیاده سازی آنها را با استفاده از Coroutine در Android بررسی خواهیم کرد.

     

    درخواست های یک باره(One shot requests)

    هر بار فراخوانی یک بار انجام می شود و به محض آماده شدن نتیجه کامل می شود. این الگو همان فراخوانی متد عادی است , فراخوانی می شود مقداری کار می کند سپس برمی گردد. به دلیل شباهت به فراخوانی متد ها ، درک آنها از streaming requests آسان تر است.

     

    هر بار فراخوانی یک One shot request انجام می شود. به محض آماده شدن نتیجه ، اجرای آن متوقف می شود.

     

    برای مثال One shot request ، نحوه بارگذاری این صفحه توسط مرورگر خود را در نظر بگیرید. هنگامی که روی لینک به این پست کلیک کردید  مرورگر شما درخواست شبکه را به سرور ارسال کرد تا صفحه بارگیری شود. هنگامی که صفحه به مرورگر شما منتقل شد ، دیگر با backend صحبت نکرد - تمام اطلاعات لازم را داشت. اگر سرور مقاله را اصلاح کند ، تغییرات جدید در مرورگر شما نمایش داده نمی شود - شما باید صفحه را رفرش کنید.

    بنابراین ، در حالی که آنها فاقد live-push درخواست های streaming هستند,درخواست های one shot بسیار قدرتمند هستند. در برنامه اندروید کارهای زیادی می توانید انجام دهید که با درخواستهای  one shot مانند واکشی ، ذخیره یا به روزرسانی داده ها قابل حل است. همچنین الگوی خوبی برای مواردی مانند مرتب سازی لیست است.

    مشکل: نمایش لیست مرتب شده

    بیایید با بررسی چگونگی نمایش لیست مرتب شده ، درخواستهای  one shot را بررسی کنیم. برای اینکه این مثال واقعی استفاده کنیم ، بیایید یک برنامه موجودی برای استفاده توسط یک کارمند در یک فروشگاه بسازیم. این مورد برای جستجوی محصولات بر اساس آخرین زمان ذخیره سازی آنها استفاده می شود آنها می خواهند بتوانند لیست را به صورت صعودی و نزولی مرتب کنند. این لیست محصولات بسیار زیادی دارد که مرتب سازی آن ممکن است تقریباً یک ثانیه طول بکشد بنابراین برای جلوگیری از مسدود کردن main thread از Coroutine استفاده خواهیم کرد!

    در این برنامه همه محصولات در یک پایگاه داده Room ذخیره می شوند. این مورد استفاده خوبی برای کارکردن است زیرا نیازی به درخواست شبکه ندارد تا بتوانیم روی الگو تمرکز کنیم. اگرچه مثال ساده تر است زیرا از شبکه استفاده نمی کند ، اما الگوهای مورد نیاز برای اجرای درخواست های  one shot را نشان می دهد. برای اجرای این درخواست با استفاده از Coroutine ، شما Coroutine ها را به ViewModel ، Repository و Dao معرفی خواهید کرد. بیایید همزمان از هرکدام آنها استفاده کنیم و ببینیم چگونه آنها را با Coroutine ادغام کنید.

    class ProductsViewModel(val productsRepository: ProductsRepository): ViewModel() {
        private val _sortedProducts = MutableLiveData<List<ProductListing>>()
        val sortedProducts: LiveData<List<ProductListing>> = _sortedProducts
    
        /**
         * Called by the UI when the user clicks the appropriate sort button
         */
        fun onSortAscending() = sortPricesBy(ascending = true)
        fun onSortDescending() = sortPricesBy(ascending = false)
    
        private fun sortPricesBy(ascending: Boolean) {
            viewModelScope.launch {
                // suspend and resume make this database request main-safe
                // so our ViewModel doesn't need to worry about threading
                _sortedProducts.value =
                    productsRepository.loadSortedProducts(ascending)
            }
        }
    }
    
    

     

    ProductsViewModel مسئول دریافت رویدادها از لایه UI و سپس درخواست به repository برای داده های به روز شده است. از LiveData برای نگه داشتن لیست مرتب شده حال حاضر برای نمایش توسط UI استفاده می کند. هنگامی که یک رویداد جدید در مرتب سازی محصولات قرار می گیرد ، یک  Coroutine جدید را برای مرتب سازی لیست شروع می کند و هنگامی که نتیجه آماده است LiveData را به روز می کند. ViewModel به طور معمول مکان مناسبی برای شروع اکثر Coroutine ها در این معماری است ، زیرا می تواند Coroutine را در ()onCleared لغو کند. اگر کاربر از صفحه خارج شود ، معمولاً هیچ استفاده ای برای کارهای بزرگ ندارد.

    این یک الگوی کلی برای Coroutine ها در Android است. از آنجا که چارچوب Android توابع suspend را نمی خواند ، شما باید در پاسخ به یک رویداد رابط کاربری با یک Coroutine هماهنگ شوید. ساده ترین راه برای این کار این است که فقط یک Coroutine جدید را هنگام ورود رویداد شروع کنید و مکان طبیعی برای آن در ViewModel است.

     

    به عنوان یک الگوی کلی  Coroutine ها را در ViewModel شروع کنید

    ViewModel برای واکشی داده ها از ProductRepository استفاده می کند

    class ProductsRepository(val productsDao: ProductsDao) {
        
        suspend fun loadSortedProducts(ascending: Boolean): List<ProductListing> {
            return if (ascending) {
                productsDao.loadProductsByDateStockedAscending()
            } else {
                productsDao.loadProductsByDateStockedDescending()
            }
        }
    }
    
    

     

    ProductsRepository یک interface منطقی برای تعامل با محصولات فراهم می کند. در این برنامه ، از آنجا که همه چیز در پایگاه داده محلی Room وجود دارد ، فقط یک  Interface برایDao فراهم می کند که دارای دو متد متفاوت برای ترتیب های مختلف است. repository بخشی اختیاری از معماری Android Architecture Components است اما اگر این لایه یا لایه مشابه آن را در برنامه خود دارید ، ترجیح می دهد متد های  regular suspendر ا نشان دهد. از آنجا که یک repository چرخه حیات طبیعی ندارد - این فقط یک object است هیچ راهی برای پاکسازی نخواهد داشت. در نتیجه  هر Coroutine که در repository شروع شود به طور پیش فرض leak دارد.

    علاوه بر جلوگیری از leak  با نمایش متد های  regular suspend استفاده مجدد از repository در زمینه های مختلف آسان است. هر چیزی که می داند چگونه یک Coroutine درست کند می تواند با loadSortedProducts تماس بگیرد. به عنوان مثال یک کار پس زمینه برنامه ریزی شده توسط کتابخانه WorkManager می تواند مستقیماً با این کار تماس بگیرد.

     

    یک repository باید ترجیح دهد متد های  regular suspend را که main-safe هستند نشان دهد.

    توجه: برخی از عملیات ذخیره پس زمینه ممکن است بخواهد پس از خروج کاربر از صفحه ادامه یابد و اجرای این ذخیره ها بدون چرخه حیات منطقی است. در اکثر موارد  viewModelScope یک انتخاب منطقی است.

     

    کدهای ProductsDao به صورت زیر است:

    @Dao
    interface ProductsDao {
        // Because this is marked suspend, Room will use it's own dispatcher
        //  to run this query in a main-safe way.
        @Query("select * from ProductListing ORDER BY dateStocked ASC")
        suspend fun loadProductsByDateStockedAscending(): List<ProductListing>
    
        // Because this is marked suspend, Room will use it's own dispatcher
        //  to run this query in a main-safe way.
        @Query("select * from ProductListing ORDER BY dateStocked DESC")
        suspend fun loadProductsByDateStockedDescending(): List<ProductListing>
    }
    

     

    ProductsDao یک قسمت از Room است که دو متد suspend را نشان می دهد. از آنجا که متد ها به حالت suspend در آمده اند ، Room از main-safe بودن آنها اطمینان حاصل می کند. این بدان معناست که می توانید مستقیماً از طریق Dispatchers.Main با آنها تماس بگیرید.

    هشدار : Coroutine که این را فراخوانی می کند در main thread خواهد بود. بنابراین اگر کار سنگینی با result ها انجام دادید مانند تبدیل آنها به یک لیست جدید باید مطمئن شوید که main thread را مسدود نمی کنید.

    توجه: Room از dispatcher مخصوص خود برای اجرای درخواست ها در یک background thread استفاده می کند. کد شما برای تماس با درخواستهای suspend از Room نباید از withContext(Dispatchers.IO) استفاده کند. این کد را پیچیده تر می کند و درخواست های شما را کندتر می کند.

    متدهای suspend در Room از main-safe برخوردار هستند و بر روی یک custom dispatcher اجرا می شوند

    الگوی درخواست یک باره(one shot request)

    این الگوی کامل ایجاد یک درخواست one shot با استفاده از Coroutine در اجزای معماری Android است. ما Coroutine هایی به ViewModel ، Repository و Room اضافه کردیم و هر لایه مسئولیت متفاوتی دارد.

    1. ViewModel یک Coroutine را روی  main thread راه اندازی می کند وقتی result راگرفت تکمیل می شود.
    2. Repository متدهای  regular suspend را در معرض دید قرار می دهد و از main-safe بودن آنها اطمینان می یابد.
    3. پایگاه داده و شبکه متدهای regular suspend را در معرض دید قرار می دهد و از main-safe بودن آنها اطمینان می یابد.

    ViewModel وظیفه شروع Coroutine ها و اطمینان از لغو آنها در صورت خروج کاربر از صفحه را دارد. کارهای سنگین را انجام نمی دهد در عوض برای انجام کارهای سنگین به لایه های دیگر اعتماد می کند. پس از دریافت نتیجه  آن را با استفاده از LiveData به UI می فرستد. از آنجا که ViewModel کار سنگینی انجام نمی دهد ، شروع به کار با  main thread می کند. اگر نتیجه بلافاصله در دسترس باشد با شروع از حالت اصلی  می تواند سریعتر به event های کاربر پاسخ دهد.

    Repository توابع  regular suspend را برای دسترسی به داده ها در معرض دید قرار می دهد. معمولاً Coroutine های طولانی مدت خود را شروع نمی کند زیرا هیچ راهی برای لغو آنها ندارد. هر زمان که Repository مجبور به انجام کارهای سنگین مانند تغییر لیست است ، باید با Context برای نشان دادن یک رابط main-safe استفاده کند.

    Data layer (شبکه یا پایگاه داده) همیشه متدهای regular suspend را نشان می دهد. مهم است که این متدهای suspend هنگام استفاده از Coroutine کاتلین main-safe باشند و Room و Retrofit هم از این الگو پیروی می کنند.

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

    ارزش دارد که لحظه ای وقت بگذارید و مطمئن شوید که این الگوها را برای درخواست های one shot درک کرده اید. این الگوی عادی برای Coroutine در Android است و شما همیشه از آن استفاده خواهید کرد.

     

    اولین گزارش bug ما!

    پس از آزمایش این راه حل ، آن را به مرحله تولید می رسانید و هفته ها همه چیز به خوبی پیش می رود تا اینکه یک گزارش bug واقعا عجیب دریافت کنید:

    موضوع:  ترتیب مرتب سازی اشتباه!

    گزارش: وقتی دکمه های مرتب سازی را واقعاً خیلی سریع کلیک می کنم ، گاهی اوقات مرتب سازی اشتباه است. این همیشه اتفاق نمی افتد.

    نگاهی می اندازید و سرتان را می خارانید. چه چیزی  اشتباهی ممکن است رخ دهد؟ الگوریتم نسبتاً ساده به نظر می رسد:

    1. مرتب سازی کاربر را درخواست کنید.
    2. مرتب سازی را در  Room dispatcherانجام دهید.
    3. نتیجه مرتب سازی را نشان دهید.

    شما وسوسه شده اید که اشکال "wontfix - دکمه ها را خیلی سریع فشار ندهید" را ببندید اما نگران هستید که چیزی نقض شود. پس از اضافه کردن دستورات ورود به سیستم و نوشتن یک آزمون برای فراخوانی انواع مختلف به یک باره سرانجام آن را فهمیدید!

    به نظر می رسد نتیجه نشان داده شده در واقع "نتیجه مرتب سازی" نیست ،نتیجه "آخرین مرتب سازی پس از تکمیل درخواست" است. هنگامی که کاربر دکمه را لمس می کند آنها همزمان چندین نوع را شروع می کنند و به هر ترتیب می توانند کار را تمام کنند!

    هنگام شروع یک Coroutine جدید در پاسخ به یک رویداد UI ، در نظر بگیرید که اگر کاربر قبل از اتمام این مورد دیگری را شروع کند ، چه اتفاقی می افتد.

    این یک concurrency bug است و در واقع هیچ ارتباطی با Coroutine ندارد.

    اگر از callback ، Rx یا حتی ExecutorService به همان روش استفاده کنیم ، bug مشابهی خواهیم داشت.

    روشهای زیادی برای رفع این مشکل هم در ViewModel و هم در Repository وجود دارد. بیایید برخی الگوها را بررسی کنیم تا اطمینان حاصل کنیم درخواست های one shot به ترتیب مورد انتظار کاربر کامل می شوند.

     

    بهترین راه حل: دکمه را غیرفعال کنید

    مشکل اساسی این است که ما دو نوع کار می کنیم. ما می توانیم با این کار فقط یک نوع کار را حل کنیم! ساده ترین راه برای این کار غیرفعال کردن دکمه های مرتب سازی برای جلوگیری از event های جدید است.

    این ممکن است یک راه حل ساده به نظر برسد ، اما واقعاً ایده خوبی است.

    کد اجرای این کار ساده ، تست آن آسان است و تا زمانی که در UI منطقی باشد ، کاملاً مشکل را برطرف می کند!

    برای غیرفعال کردن دکمه ها ، به UI بگویید که درخواست مرتب سازی در داخل sortPrices اتفاق می افتد مانند این:

    // Solution 0: Disable the sort buttons when any sort is running
    
        class ProductsViewModel(val productsRepository: ProductsRepository): ViewModel() {
            private val _sortedProducts = MutableLiveData<List<ProductListing>>()
            val sortedProducts: LiveData<List<ProductListing>> = _sortedProducts
    
            private val _sortButtonsEnabled = MutableLiveData<Boolean>()
            val sortButtonsEnabled: LiveData<Boolean> = _sortButtonsEnabled
    
            init {
                _sortButtonsEnabled.value = true
            }
    
            /**
             * Called by the UI when the user clicks the appropriate sort button
             */
            fun onSortAscending() = sortPricesBy(ascending = true)
            fun onSortDescending() = sortPricesBy(ascending = false)
    
            private fun sortPricesBy(ascending: Boolean) {
                viewModelScope.launch {
                    // disable the sort buttons whenever a sort is running
                    _sortButtonsEnabled.value = false
                    try {
                        _sortedProducts.value =
                            productsRepository.loadSortedProducts(ascending)
                    } finally {
                        // re-enable the sort buttons after the sort is complete
                        _sortButtonsEnabled.value = true
                    }
                }
            }
        }
    

    غیرفعال کردن دکمه ها در حالی که مرتب سازی با استفاده از  _sortButtonsEnabled در  sortPricesByفعال است.

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

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

    مهم: این کد یک مزیت شروع به کار اصلی را نشان می دهد دکمه ها در پاسخ به یک کلیک فوراً غیرفعال می شوند. اگر بین dispatcher ها سوئیچ کرده اید یک کاربر با انگشت سریع روی تلفن هایی که کند کار میکنند می تواند بیش از یک کلیک ارسال کند!

    الگوهای همزمانی(Concurrency patterns)

    چند بخش بعدی موضوعات پیشرفته را بررسی می کند  و اگر تازه شروع به کار با Coroutine ها می کنید ، نیازی نیست که بلافاصله آنها را درک کنید. غیرفعال کردن ساده دکمه بهترین راه حل برای اکثر مشکلاتی است که با آن روبرو خواهید شد.

    برای بقیه این مقاله ، ما روش هایی را برای استفاده از  Coroutine برای فعال نگه داشتن دکمه کشف خواهیم کرد اما اطمینان حاصل خواهیم کرد که درخواست های one shot به ترتیبی اجرا می شوند که کاربر را شگفت زده نمی کند. ما می توانیم این کار را با اجتناب از concurrency تصادفی با کنترل زمان اجرای Coroutine (یا اجرا نشدن) انجام دهیم.

    در اینجا سه الگوی اساسی وجود دارد که می توانید برای one shot request استفاده کنید تا مطمئن شوید دقیقاً یک درخواست در یک زمان اجرا می شود.

    1. قبل از شروع کار بیشتر ، کار قبلی را لغو کنید.
    2. کار بعدی را در صف قرار دهید و قبل از شروع درخواست دیگر منتظر تکمیل درخواست های قبلی باشید.
    3. اگر از قبل درخواستی در حال اجرا است ، به کار قبلی بپیوندید ، به جای شروع درخواست دیگر ، آن را برگردانید

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

    راه حل شماره 1: کار قبلی را لغو کنید

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

    برای لغو درخواست قبلی ، باید به نوعی آن را پیگیری کنیم. تابع cancelPreviousThenRun در اصل دقیقاً همین کار را می کند.

     

    بیایید نگاهی به نحوه استفاده از آن برای رفع اشکال بیندازیم:

    // Solution #1: Cancel previous work
    
    // This is a great solution for tasks like sorting and filtering that
    // can be cancelled if a new request comes in.
    
        class ProductsRepository(val productsDao: ProductsDao, val productsApi: ProductsService) {
            var controlledRunner = ControlledRunner<List<ProductListing>>()
    
            suspend fun loadSortedProducts(ascending: Boolean): List<ProductListing> {
                // cancel the previous sorts before starting a new one
                return controlledRunner.cancelPreviousThenRun {
                    if (ascending) {
                        productsDao.loadProductsByDateStockedAscending()
                    } else {
                        productsDao.loadProductsByDateStockedDescending()
                    }
                }
            }
        }
    
    

     

    با استفاده از cancelPreviousThenRun برای اطمینان از اینکه فقط یک نوع مرتب سازی اجرا می شود.

    پیاده سازی برای cancelPreviousThenRun در اصل روش خوبی برای دیدن چگونگی پیگیری کار در حال انجام است.

    suspend fun cancelPreviousThenRun(block: suspend () -> T): T {
        // If there is an activeTask, cancel it because it's result is no longer needed
        activeTask?.cancelAndJoin()
    
        // ...
    }
    

     

    راه حل شماره 2: صف کار بعدی

    برای رفع اشکالات همزمانی یک راه حل وجود دارد که همیشه کارساز است.

    فقط در صف درخواست ها قرار دهید تا هر بار فقط یک اتفاق بیفتد! درست مانند یک صف یا یک صف در یک فروشگاه ، درخواست ها نیز به ترتیب شروع شده یکی پس از دیگری اجرا می شوند.

    برای این مشکل خاص در مرتب سازی ، لغو احتمالاً بهتر از نوبت دهی است ، اما لازم است درباره آن صحبت کنید زیرا همیشه کارساز است.

    // Solution #2: Add a Mutex
    
    // Note: This is not optimal for the specific use case of sorting
    // or filtering but is a good pattern for network saves.
    
        class ProductsRepository(val productsDao: ProductsDao, val productsApi: ProductsService) {
            val singleRunner = SingleRunner()
    
            suspend fun loadSortedProducts(ascending: Boolean): List<ProductListing> {
                // wait for the previous sort to complete before starting a new one
                return singleRunner.afterPrevious {
                    if (ascending) {
                        productsDao.loadProductsByDateStockedAscending()
                    } else {
                        productsDao.loadProductsByDateStockedDescending()
                    }
                }
            }
        }
    

     

    هر زمان که مرتب سازی جدیدی انجام می شود ، از نمونه SingleRunner برای اطمینان از اجرای همزمان فقط یک نوع استفاده می کند.

    از یک Mutex استفاده می کند ، که یک بلیط (یا قفل) است ، که یک Coroutine برای ورود به بلوک باید دریافت کند. اگر یک Coroutine دیگر در حالی که یکی در حال اجرا بود امتحان می کرد ، خود را به حالت suspend در می آورد تا زمانی که تمام Coroutine های معلق با Mutex انجام شوند.

    Mutex به شما این امکان را می دهد که هر بار فقط یک Coroutine اجرا کنید و آنها به ترتیب شروع کار خود به پایان می رسند.

    راه حل 3: به کارهای قبلی بپیوندید

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

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

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

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

    بیایید برخی از کدها را با استفاده از joinPreviousOrRun از فهرست برای مثال در مورد چگونگی عملکرد بررسی کنیم:

    class ProductsRepository(val productsDao: ProductsDao, val productsApi: ProductsService) {
        var controlledRunner = ControlledRunner<List<ProductListing>>()
    
        suspend fun fetchProductsFromBackend(): List<ProductListing> {
            // if there's already a request running, return the result from the 
            // existing request. If not, start a new request by running the block.
            return controlledRunner.joinPreviousOrRun {
                val result = productsApi.getProducts()
                productsDao.insertAll(result)
                result
            }
        }
    }
    

     

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

    می توانید نحوه کارکرد این کار را در ابتدای joinPreviousOrRun مشاهده کنید - در صورت وجود هر Task فعال ، فقط نتیجه قبلی را برمی گرداند:

    suspend fun joinPreviousOrRun(block: suspend () -> T): T {
        // if there is an activeTask, return it's result and don't run the block
        activeTask?.let {
            return it.await()
        }
        // ...
    }
    

    این الگو برای درخواست هایی مانند واکشی محصولات توسط id به خوبی مقیاس می شود.

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

    مقاله 3 قسمتی coroutine در اندروید به پایان رسید امیدوارم که براتون مفید واقع شده باشد.

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


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

    ارسال دیدگاه

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


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