مقالات باگتو

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در اندروید به پایان رسید امیدوارم که براتون مفید واقع شده باشد.

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

تگ‌ها
اشتراک

0 نظرات

    ;