Warum wird viewModelScope.launch standardmäßig im Hauptthread ausgeführt?

Lesezeit: 5 Minuten

Während ich Coroutinen lernte und wie man sie richtig in einer Android-App verwendet, fand ich etwas, worüber ich überrascht war.

Beim Starten einer Coroutine mit viewModelScope.launch { } und beim Festlegen eines Haltepunkts im Launch-Lambda bemerkte ich, dass meine App nicht mehr reagierte, weil sie sich immer noch im Hauptthread befand.

Das verwirrt mich, weil die Dokumente von viewModelScope.launch { } eindeutig festgehalten:

Startet eine neue Coroutine, ohne den aktuellen Thread zu blockieren

Ist der aktuelle Thread nicht der Hauptthread? Was ist der ganze Zweck des Starts, wenn er standardmäßig nicht in einem anderen Thread ausgeführt wird?

Ich konnte es auf einem anderen Thread mit ausführen viewModelScope.launch(Dispatchers.IO){ } was funktioniert, wie ich erwartet hatte, nämlich in einem anderen Thread.

Was ich versuche, von der zu erreichen launch Die Methode besteht darin, ein Repository aufzurufen und einige IO-Arbeiten auszuführen, nämlich einen Webservice aufzurufen und die Daten in einer Raumdatenbank zu speichern. Also dachte ich an einen Anruf viewModelScope.launch(Dispatchers.IO){ } die ganze Arbeit in einem anderen Thread erledigen und am Ende das LiveData-Ergebnis aktualisieren.

viewModelScope.launch(Dispatchers.IO){
liveData.postValue(someRepository.someWork())
}

Also meine zweite Frage ist, ist das der richtige Weg?

  • Dank dieser Frage und nach einem Jahr der Annahme viewModelScope.launch ausreichen würde, um die Arbeit von Main zu verschieben, habe ich meine Methode aktualisiert, um den darin eingeschlossenen Funktionsrumpf auszuführen withContext(Dispatchers.IO) und plötzlich schoss meine App-Performance in die Höhe!

    – rmirabelle

    19. Mai 2021 um 20:47 Uhr

ViewModelScope.launch { } läuft im Haupt-Thread, gibt Ihnen aber auch die Möglichkeit, andere Dispatcher auszuführen, sodass Sie UI- und Hintergrundoperationen synchron ausführen können.

Für dein Beispiel:

fun thisWillRunOnMainThread() {

    viewModelScope.launch { 

        //below code will run on UI thread.
        showLoadingOnUI()

        //using withContext() you can run a block of code on different dispatcher
        val result = novel.id = withContext(Dispatchers.IO) {
            withsomeRepository.someWork()
        }

        //The below code waits until the above block is executed and the result is set.
        liveData.value = result
        finishLoadingOnUI()
    }
}

Als weitere Referenz würde ich sagen, dass es einige nette Artikel gibt, die Ihnen helfen werden, dieses Konzept zu verstehen.

Mittlerer Link, der es wirklich ordentlich erklärt.

  • So wurde mein Code geschrieben, aber nachdem der benötigte Wert gespeichert wurde, kann ich die Daten nicht anzeigen, da die Steuerung nicht vom ViewModel aus aktiv wird. Hänge seit Wochen daran fest

    – Meenohara

    30. Juli 2021 um 13:27 Uhr

Benutzeravatar von EpicPandaForce
EpicPandaForce

Also meine zweite Frage ist, ist das der richtige Weg?

Ich würde erwarten, dass zwei Dinge in Ihrem derzeitigen Ansatz anders sind.

1.) Der erste Schritt wäre, den Scheduler des Hintergrundbetriebs über zu definieren withContext.

class SomeRepository {
    suspend fun doWork(): SomeResult = withContext(Dispatchers.IO) {
        ...
    }
}

Auf diese Weise wird die Operation selbst in einem Hintergrundthread ausgeführt, aber Sie haben Ihren ursprünglichen Bereich nicht gezwungen, “off-thread” zu sein.

2.) Jetpack Lifecycle KTX bietet die liveData { Coroutine Builder, damit Sie es nicht tun müssen postValue manuell dazu.

val liveData: LiveData<SomeResult> = liveData {
    emit(someRepository.someWork())
}

Was Sie in einem ViewModel so verwenden würden:

val liveData: LiveData<SomeResult> = liveData(context = viewModelScope.coroutineContext) {
    withContext(Dispatchers.IO) {
        emit(someRepository.someWork())
    }
}

Und jetzt können Sie das Laden von Daten automatisch durch Beobachten auslösen und müssen nicht manuell aufrufen viewModelScope.launch {}.

  • viewModelScope.launch{ } wird nur beim ersten Aufruf ausgeführt. Zweitens ruft es nicht an

    – Nitisch

    15. Februar 2021 um 13:02 Uhr


  • @Nitish nicht sicher, worauf Sie sich genau beziehen, meinen Sie dann, wie Sie zum Bildschirm navigieren liveData { aktiv wird und den Baustein ausführt?

    – EpicPandaForce

    15. Februar 2021 um 13:04 Uhr

  • Danke für die schnelle Antwort. Eigentlich rufe ich eine Funktion in onResume of fragment auf. In dieser Funktion erhalte ich einige Daten aus der Raumdatenbank. In dieser Funktion viewModelScope.launch {withContext(Dispachers.IO){ db.cbDao().getAll() } }. aber mein Debugger geht nicht in den Startbereich.

    – Nitisch

    15. Februar 2021 um 13:16 Uhr


  • Ja, der Debugger ist nicht sehr gut, wenn es um Coroutinen oder insbesondere Flows geht

    – EpicPandaForce

    15. Februar 2021 um 13:50 Uhr

  • @amos du hast recht, viewModelScope.coroutineContext übergeben werden soll liveData {. Aktualisiert.

    – EpicPandaForce

    13. September 2021 um 11:19 Uhr

Die Idee dahinter, dass der Hauptthread standardmäßig ist, ist, dass Sie UI-Operationen ausführen können, ohne den Kontext ändern zu müssen. Es ist eine Konvention, für die sich die Autoren der Kotlin-Koroutinenbibliothek entschieden haben

Angenommen, wenn der Start standardmäßig auf einem IO-Thread ausgeführt wird, würde der Code so aussehen

viewmodelScope.launch {
  val response = networkRequest() 
  withContext(Dispatchers.Main) {
    renderUI(response)
  } 
}

Angenommen, wenn der Start standardmäßig im Standardthread ausgeführt wird, würde der Code so aussehen

viewmodelScope.launch {
  val response: Response = null
  withContext(Dispatchers.IO) {
     response = networkRequest()
  }
  withContext(Dispatchers.Main) {
    renderUI(response)
  } 
}

Da der Standardstart im Hauptthread ist, müssen Sie jetzt Folgendes tun

viewmodelScope.launch {
  val response: Response = null
  withContext(Dispatchers.IO) {
     response = networkRequest()
  }
  renderUI(response)
}

Um zu vermeiden, dass der unordentliche Code die Antwort mit null initialisiert, können wir die networkRequest auch so machen, dass sie die Geschäftslogik der networkRequest()-Funktion in withContext(Dispatchers.IO) aussetzt und umschließt, und so schreiben viele Leute auch ihre networkRequest()-Funktion ! Hoffe das macht Sinn

Einer der Hauptgründe, warum es im Main-Thread läuft, ist, dass es praktischer für die allgemeine Verwendung in ViewModel ist, wie murali kurapati schrieb. Es war eine Designentscheidung.

Es ist auch wichtig zu beachten, dass alle Suspendierungsfunktionen “main safe” gemäß sein sollten Best Practices. Das bedeutet, dass Ihr Repository den Coroutine-Kontext entsprechend ändern sollte, etwa so:

class someRepository(private val ioDispatcher: CoroutineDispatcher) {
    suspend fun someWork() {
        withContext(ioDispatcher) {
            TODO("Heavy lifting")
        }
    }
}

1394880cookie-checkWarum wird viewModelScope.launch standardmäßig im Hauptthread ausgeführt?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy