Kotlin ожидает сопрограммы в цикле for перед переходом к следующему индексу

#kotlin #retrofit2 #kotlin-coroutines

#котлин #модернизация 2 #kotlin-сопрограммы

Вопрос:

Привет, я использую цикл for для вызова метода сопрограммы (который вызывает retrofit), и для каждого цикла я хочу, чтобы метод сопрограммы завершался (после ответа retrofit), но мой цикл, похоже, продолжает работать, не дожидаясь завершения метода сопрограммы… Ниже приведен мой метод цикла:-

 fun forwardFailedSMS(context: Context) {
            var failed = getFailedSms(context)

            failed.forEachIndexed { index, f ->    
                println("NOW ${index}")
                GlobalScope.launch(Dispatchers.IO) {
                    var time = measureTimeMillis {
                        val fn = async {
                            callForwardAPI(context, f)
                        }
                        val result = fn.await()
                    }
                }
                //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                //I want the above to finish before going to next index...
            }

            refreshSmsList(context!!)
        }
 

Ниже приведена функция callForwardAPI, которая использует Retrofit для вызова API:-

 suspend fun callForwardAPI(context: Context,sms: SmsData) {
        val databaseHandler: DatabaseHandler = DatabaseHandler(context)

        val retrofit = Retrofit.Builder()
            .baseUrl("https://backend.mydomain.com/")
            .addConverterFactory(GsonConverterFactory.create())
            .build()
            .create(SMSService::class.java)

        val api = GlobalScope.async {
            val response = retrofit.postForwardSMS(
                sms.sender,
                sms.message
            ).awaitResponse()

            if (response.isSuccessful) {
               println("DONE SUCCESS ${sms.message}")
            }
        }
        api.await()
    }
 

Журнал «СЕЙЧАС» зацикливается и печатается до того, как будет напечатано сообщение «ВЫПОЛНЕНО УСПЕШНО» для этого конкретного цикла…

Ответ №1:

Вы должны переместить .launch{ } метод за пределы forEach . В настоящее время происходит то, что ваши методы приостановки будут приостановлены, но внутри запуска, так что for может продолжаться.

Если вы переместите запуск за пределы for, ваш цикл for также будет приостановлен в каждой точке приостановки

Ответ №2:

ваш вызов службы содержит сопрограмму, поэтому вам не нужно использовать сопрограмму в вашей функции forwardFailedSMS. вы создаете два независимых асинхронных задания. Я предполагаю, что использование оперативных данных и наблюдателя может решить ваш случай.

вам необходимо изменить свою функцию forwardFailedSMS, показанную ниже:

 lateinit var failedListSize: Int
lateinit var serviceCallResponseCount: Int

fun forwardFailedSMS(context: Context) {
            var failed = getFailedSms(context)
            failedListSize = failed.size
            serviceCallResponseCount = 0

            failed.forEachIndexed { index, f ->    
                println("NOW ${index}")
                callForwardAPI(context, f)
            }

            
        }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        // I assume that you are using viewmodel and fragment. If you use another pattern, you can change onViewCreate to another lifecycle function.
        viewModel.myLiveData.observe(viewLifeCycleOwner, Observer{
                println("NOW ${index} observed response")
                serviceCallResponseCount  
                if (serviceCallResponseCount == failedListSize){
                    refreshSmsList(context!!)
                }

        }
}
 

и ваш callForwardAPI для:

 val myLiveData: MutableLiveData<QueryOnlinePolicyResponse> = MutableLiveData()
suspend fun callForwardAPI(context: Context,sms: SmsData) {
        val databaseHandler: DatabaseHandler = DatabaseHandler(context)

        val retrofit = Retrofit.Builder()
            .baseUrl("https://backend.mydomain.com/")
            .addConverterFactory(GsonConverterFactory.create())
            .build()
            .create(SMSService::class.java)

        val api = GlobalScope.async {
            val response = retrofit.postForwardSMS(
                sms.sender,
                sms.message
            ).awaitResponse()
            // I put postValue here because we are waiting same response count with fail in our observer
            myLiveData.postValue(response)
            if (response.isSuccessful) {
               println("DONE SUCCESS ${sms.message}")
            }
        }
        api.await()
    }
 

Я считаю, что этот шаблон решит ваш случай. Я не мог попробовать, я использую редактор. Простите меня, если есть какие-то ошибки.

Ответ №3:

Если вы хотите forwardFailedSMS() сохранить обычный метод блокировки, вы можете использовать runBlocking вместо launch и async

 ...
runBlocking(Dispatchers.IO) {
    failed.forEachIndexed { index, f ->    
        println("NOW ${index}")
        callForwardAPI(context, f)
    }
}
...