Как использовать обратный вызов в определенном потоке, чтобы дождаться его в основном потоке?

# #android #firebase #kotlin #kotlin-coroutines

Вопрос:

Первый вопрос здесь, я сделаю все, что в моих силах.

У меня есть класс данных, который извлекает объект данных с помощью firestore при создании. Я сделал некоторый код для сеттеров с помощью сопрограмм. Я не уверен в своем решении, но оно работает. Однако для добытчиков я изо всех сил пытаюсь дождаться инициализации.

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

 Log.d("THREAD", "Execution thread1: " Thread.currentThread().name)
 

Для сеттера я использую сопрограмму в useTask, чтобы не блокировать основной поток. И мьютекс для блокировки этой сопрограммы до тех пор, пока не будет выполнена инициализация в инициализации. Не уверен насчет ожидания инициализации, но это работает.

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

Я прочитал много документации о сопрограмме, области действия, блокировке выполнения, потоке и т. Д., Но все путается в моей голове.

 class Story(val id: String) :  BaseObservable() {

    private val storyRef = StoryHelper.getStoryRef(id)!!
    private var isInitialized = false
    private val initMutex = Mutex(true)

    @get:Bindable
    var dbStory: DbStory? = null

    init {
         storyRef.get().addOnCompleteListener { task ->
            if (task.isSuccessful amp;amp; task.result != null) {
                dbStory = snapshot.toObject(DbStory::class.java)!!
                if (!isInitialized) {
                    initMutex.unlock()
                    isInitialized = true
                }
                notifyPropertyChanged(BR.dbStory)
            }
        }
    }

    fun interface StoryListener {
        fun onEvent()
    }

    private fun useTask(function: (task: Task) -> Unit): Task {
        val task = Task()
        GlobalScope.launch {
            waitInitialisationSuspend()
            function(task)
        }
        return task
    }

    private suspend fun waitInitialisationSuspend()
    {
        initMutex.withLock {
            // no op wait for unlock mutex
        }
    }

    fun typicalSetFunction(value: String) : Task {
        return useTask { task ->
            storyRef.update("fieldName", value).addOnSuccessListener {
                task.doEvent()
            }
        }
    }

    fun typicalGetFunction(): String
    {
        var result = ""

        // want something to wait the callback in the init.

        return result
    }
}
 

Блокировка запуска, похоже, блокирует основной поток, поэтому я не могу его использовать, если обратный вызов все еще использует основной поток.
Та же проблема возникает, если я использую цикл while в основном потоке.

#1

 runBlocking {
     initMutex.withLock {
         result = dbStory!!.value
     }
}
 

#2

 while (!isInitialized){
}
result = dbStory!!.value
 

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

 private val scope = CoroutineScope(Dispatchers.IO   SupervisorJob())
 
 scope.launch() {
            reference.get().addOnCompleteListener { task ->
 

В геттере я должен работать с основным потоком. Решение, возможно, состоит в том, чтобы перевести выполнение обратного вызова в другой поток, но я не знаю, как это сделать. И, может быть, есть лучшее решение.

Другим решением будет возможность дождаться обратного вызова в главном потоке, не блокируя обратный вызов, но у меня нет решения для этого.

Есть какие-нибудь идеи ?

Ответ №1:

Я искал много решений, и вывод таков: не делайте этого.

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

Даже использование другого потока для обратного вызова (что вы можете сделать с исполнителем), я думаю, здесь плохая идея. Хороший способ дождаться окончания задачи при обратном вызове-получить задачу и использовать:

 Tasks.await(initTask)
 

Но это запрещено в основном потоке. Android мешает вам делать плохой дизайн здесь.

Мы должны иметь дело с асинхронным способом управления базой данных firebase, это лучший способ сделать это. Я все еще могу использовать свой кэш для данных. Здесь я ждал, чтобы отобразить диалоговое окно с текстом, который я получаю в firebase. Таким образом, я могу просто асинхронно отображать диалоговое окно при извлечении текстовых данных. Если кэш доступен, он будет его использовать.

Имейте также в виду, что у firebase, похоже, есть какой-то API для использования кэша.