
Ich habe mich entschlossen, über einige Dinge zu schreiben, die meiner Meinung nach bei der Verwendung von Kotlin Coroutine vermieden werden sollten und sollten.
Schließen Sie asynchrone Aufrufe in coroutineScope ein oder verwenden Sie SupervisorJob, um Ausnahmen zu behandeln
Wenn im async
Block eine Ausnahme auftreten kann, verlassen Sie sich nicht auf den try/catch
.
val job: Job = Job() val scope = CoroutineScope(Dispatchers.Default + job)
Im obigen Beispiel startet die Funktion doWork
eine neue Coroutine (1), die möglicherweise eine nicht behandelte Ausnahme auslöst. Wenn Sie versuchen, doWork
mit einem try/catch
(2) try/catch
zu doWork
die Anwendung immer noch ab.
Dies liegt daran, dass der Ausfall einer untergeordneten Komponente des Jobs zum sofortigen Ausfall des übergeordneten Elements führt.
Eine Möglichkeit, den Fehler zu vermeiden, ist die Verwendung von SupervisorJob
(1).
Ein Fehler oder ein Abbruch der Ausführung der untergeordneten Komponente führt nicht zum Ausfall der übergeordneten Komponente und hat keine Auswirkungen auf andere Komponenten.
val job = SupervisorJob()
Hinweis : Dies funktioniert nur, wenn Sie Ihren asynchronen Coroutine-Aufruf explizit mit SupervisorJob
starten. Daher stürzt der folgende Code Ihre Anwendung immer noch ab, da async
als Teil der übergeordneten Coroutine (1) ausgeführt wird.
val job = SupervisorJob() val scope = CoroutineScope(Dispatchers.Default + job) fun loadData() = scope.launch { try { async {
Eine andere Möglichkeit, einen Absturz zu vermeiden, der vorzuziehen ist, besteht darin, async
in coroutineScope
(1) async
. Wenn nun innerhalb von async
eine Ausnahme auftritt, werden alle anderen in diesem Bereich erstellten Coroutinen abgebrochen, ohne den externen Bereich zu berühren. (2)
val job = SupervisorJob() val scope = CoroutineScope(Dispatchers.Default + job)
Darüber hinaus können Sie Ausnahmen innerhalb des async
Blocks behandeln.
Verwenden Sie den Hauptmanager für Root-Coroutinen
Wenn Sie Hintergrundarbeiten ausführen und die Benutzeroberfläche in Ihrer Root-Coroutine aktualisieren müssen, starten Sie sie mit dem Haupt-Dispatcher.
val scope = CoroutineScope(Dispatchers.Default)
Im obigen Beispiel starten wir die Root-Coroutine mit dem CoroutineScope
Dispatcher in CoroutineScope
(1). Bei diesem Ansatz müssen wir jedes Mal, wenn wir die Benutzeroberfläche aktualisieren müssen, den Kontext wechseln (2).
In den meisten Fällen ist es vorzuziehen, CoroutineScope
sofort mit dem Haupt-Dispatcher zu erstellen, was zu einer Vereinfachung des Codes und einer weniger expliziten CoroutineScope
.
val scope = CoroutineScope(Dispatchers.Main) fun login() = scope.launch { view.showLoading() withContext(Dispatcher.IO) { networkClient.login(...) } view.hideLoading() }
Vermeiden Sie unnötiges Async / Warten
Wenn Sie die async
Funktion verwenden und sofort auf wait await
, sollten Sie dies beenden.
launch { val data = async(Dispatchers.Default) { }.await() }
Wenn Sie den Kontext der Coroutine wechseln und die übergeordnete Coroutine sofort aussetzen withContext
ist withContext
der am besten withContext
Weg.
launch { val data = withContext(Dispatchers.Default) { } }
Unter Leistungsgesichtspunkten ist dies kein so großes Problem (selbst wenn man bedenkt, dass async
eine neue Coroutine erstellt, um die Aufgabe zu erledigen), aber semantisch bedeutet async
, dass Sie mehrere Coroutinen im Hintergrund ausführen und erst dann auf sie warten möchten.
Vermeiden Sie die Stornierung von Jobs
Wenn Sie Coroutine abbrechen müssen, brechen Sie den Auftrag nicht ab.
class WorkManager { val job = SupervisorJob() val scope = CoroutineScope(Dispatchers.Default + job) fun doWork1() { scope.launch { } } fun doWork2() { scope.launch { } } fun cancelAllWork() { job.cancel() } } fun main() { val workManager = WorkManager() workManager.doWork1() workManager.doWork2() workManager.cancelAllWork() workManager.doWork1()
Das Problem mit dem obigen Code ist, dass wir den Auftrag in einen abgeschlossenen Zustand versetzen, wenn wir ihn abbrechen. Coroutinen, die als Teil eines abgeschlossenen Jobs gestartet wurden, werden nicht ausgeführt (1).
Wenn Sie alle Coroutinen in einem bestimmten Bereich rückgängig machen möchten, können Sie die Funktion cancelChildren
verwenden. Darüber hinaus empfiehlt es sich, einzelne Jobs abzubrechen (2).
class WorkManager { val job = SupervisorJob() val scope = CoroutineScope(Dispatchers.Default + job) fun doWork1(): Job = scope.launch { }
Vermeiden Sie das Schreiben einer Pausenfunktion mit dem impliziten Dispatcher
Schreiben Sie nicht die suspend
Funktion, deren Ausführung vom jeweiligen Coroutine-Manager abhängt.
suspend fun login(): Result { view.showLoading() val result = withContext(Dispatcher.IO) { someBlockingCall() } view.hideLoading() return result }
Im obigen Beispiel ist die Anmeldefunktion eine Suspendierungsfunktion und schlägt fehl, wenn Sie sie von einer Coroutine aus starten, die der Hauptverteiler nicht verwendet.
launch(Dispatcher.Main) {
CalledFromWrongThreadException: Nur der Quell-Thread, der die Ansichtskomponentenhierarchie erstellt hat, hat Zugriff darauf.
Erstellen Sie Ihre Suspendierungsfunktion so, dass sie von jedem Coroutine-Manager ausgeführt werden kann.
suspend fun login(): Result = withContext(Dispatcher.Main) { view.showLoading() val result = withContext(Dispatcher.IO) { someBlockingCall() } view.hideLoading() return result }
Jetzt können wir unsere Login-Funktion von jedem Dispatcher aus aufrufen.
launch(Dispatcher.Main) {
Vermeiden Sie die Verwendung des globalen Bereichs
Wenn Sie GlobalScope
überall in Ihrer Android-Anwendung verwenden, sollten Sie dies beenden.
GlobalScope.launch {
Der globale Bereich wird verwendet, um Coroutinen der obersten Ebene zu starten, die während der gesamten Lebensdauer der Anwendung ausgeführt werden und nicht vorzeitig abgebrochen werden.
Der Anwendungscode sollte normalerweise das anwendungsspezifische CoroutineScope verwenden. Daher wird dringend davon abgeraten, Async zu verwenden oder in GlobalScope zu starten .
In Android kann Coroutine leicht auf den Lebenszyklus einer Aktivität, eines Fragments, einer Ansicht oder eines ViewModels beschränkt werden.
class MainActivity : AppCompatActivity(), CoroutineScope { private val job = SupervisorJob() override val coroutineContext: CoroutineContext get() = Dispatchers.Main + job override fun onDestroy() { super.onDestroy() coroutineContext.cancelChildren() } fun loadData() = launch {