#kotlin #asynchronous
Вопрос:
Я новичок в котлине и сопрограммах.Я работал над клиент-серверной частью приложения для Android.Я пытаюсь получить возможности маршрутизации, используя следующий код
suspend fun getRouterCapabilities(): String? = coroutineScope {
lateinit var routerRtpCapabilities: JSONObject
val job = launch {
socket?.emit("getRouterRtpCapabilities", "", Ack { args ->
routerRtpCapabilities = args[0] as JSONObject
Log.d(TAG, routerRtpCapabilities!!.getString("error"))
})
}
job.join()
Log.d(TAG, "$routerRtpCapabilities")
return@coroutineScope routerRtpCapabilities.toString()
}
В приведенном выше коде я могу сохранить и распечатать значение routerRtpCapabilites внутри функции emit().Но я получил указанную ниже ошибку, когда попытался получить к ней доступ извне функции emit().
котлин.Неинициализированное исключение propertyaccessexception: свойство lateinit routerRtpCapabilities не было инициализировано
А также я не уверен в том, как здесь используются сопрограммы.Пожалуйста, поправьте меня, если я упустил что-то важное.
Комментарии:
1. Какую библиотеку вы используете для сетевого взаимодействия? Я временно удалил свой ответ, потому что заметил
emit
, что это функция, основанная на обратном вызове, поэтому мой пример неверен.2. «io.сокет.клиент. Сокет» Я использовал эту библиотеку для подключений к WebSocket. В данном коде сокет является экземпляром объекта сокета. socket.emit( ) запрашивает конкретное событие(в данном случае это getRouterCapabilities) и отправляет данные (здесь их нет. Вот почему я передал пустую строку в качестве второго параметра) серверу, и ответ сервера получен в виде аргументов. Из аргументов мне нужно получить возможности маршрутизации и вернуть это значение в качестве возвращаемого значения функции. Но я получаю возвращаемое значение как null из-за того, что функция emit() асинхронна, и это то, что мне нужно решить
3. Хорошо, я обновил ответ. Я лишь мельком взглянул на библиотеку, которую вы использовали, поэтому я не тестировал свой код, но это должно вывести вас на правильный путь.
Ответ №1:
Причина, по которой вы job.join()
не ждете подтверждения, заключается в том, что emit
функция запускает асинхронное действие и немедленно возвращается. Обратный вызов с результатом вызывается позже. Но задание сопрограммы не знает, нужно ли ждать какого-либо обратного вызова, поэтому оно немедленно завершается, прежде чем подтверждение будет получено некоторое время спустя.
Если у вас есть библиотека с асинхронной функцией с обратным вызовом, вы можете преобразовать ее в функцию приостановки, чтобы ее можно было легко использовать в сопрограммах. Вот как вы могли бы преобразовать эту функцию выделения:
/** Emits the [event] and suspends until acknowledgement is received. Returns the
acknowledgement arguments. */
suspend fun Socket.awaitEmit(event: String, vararg arg: Any): Array<out Any?> =
suspendCoroutine { continuation ->
emit(event, *arg, Ack { args ->
continuation.resume(args)
})
}
Некоторые библиотеки, такие как Retrofit и Firebase, поставляются с версиями функций приостановки своих асинхронных функций, и для них потребуется выполнить описанный выше шаг.
Правильная функция приостановки может быть вызвана из других функций приостановки и в сопрограммах, не делая ничего особенного, например, не помещая ее в другую сопрограмму или withContext
блок.
suspend fun getRouterCapabilities(): String? = routerCababilitiesMutex.withLock {
val acknowledgement = socket?.awaitEmit("getRouterRtpCapabilities", "")
acknowledgement ?: return null // must have null socket
val result = acknowledgement[0] as? JSONObject
Log.d(TAG, result?.getString("error") ?: "result is null")
return result?.toString()
}
Его !!
следует изменить, потому что это небезопасно.
Чтобы вызвать функцию приостановки из действия, вы должны сделать это в lifecycleScope
сопрограмме, например:
mic.setOnClickListener{
lifecycleScope.launch {
val routerCap = someOtherClass.getRouterCapabilities()
// Do stuff with routerCap here.
}
}
Изменить: Как бы я кэшировал значение в вашем классе модели представления. Вы можете использовать мьютекс, чтобы убедиться, что он не извлекается избыточно.
private var routerCapabilities: String? = null
private val routerCapabilitiesMutex = Mutex()
suspend fun getRouterCapabilities(): String? = routerCapabilitiesMutex.withLock {
routerCapabilities?.let { return it }
val acknowledgement = socket?.awaitEmit("getRouterRtpCapabilities", "")
acknowledgement ?: return null // must have null socket
val result = acknowledgement[0] as? JSONObject
Log.d(TAG, result?.getString("error") ?: "result is null")
result?.toString().also { routerCapabilities = it }
}
Комментарии:
1. Спасибо за ваш ответ. Я хочу вызвать этот метод в onCreate() другого класса при нажатии на микрофон. Чтобы быть точным, я хочу вызвать его на mic.setOnClickListener{ //val routerCap = getRouterCapabilities() }.Можете ли вы дать мне представление о том, как это можно сделать? Спасибо!!
2. Большое спасибо. Это действительно сработало для меня. У меня есть еще одно сомнение. Как я могу использовать значение routerCap вне блока lifecycleScope?
3. На самом деле нет чистого способа сделать это. Вы можете присвоить ему свойство, обнуляемое, но тогда в любом месте, где вам нужно значение, вам придется проверить, является ли оно нулевым, и если оно не равно нулю, запустите другую сопрограмму для его извлечения. Вероятно, самый чистый способ справиться с этим-иметь свойство nullable в классе с функцией suspend и позволить функции suspend возвращать значение в свойстве, если оно еще не было получено. Затем всегда используйте функцию приостановки, чтобы получить значение, и оно быстро вернется, если оно было получено ранее. Но вы всегда использовали
lifecycleScope.launch
бы это, чтобы получить его.