
Decidi escrever sobre algumas coisas que, na minha opinião, são e não devem ser evitadas ao usar a corotina Kotlin.
Quebra de chamadas assíncronas em coroutineScope ou use SupervisorJob para manipular exceções
Se uma exceção pode ocorrer no bloco async , não confie no try/catch .
val job: Job = Job() val scope = CoroutineScope(Dispatchers.Default + job)
No exemplo acima, a função doWork inicia uma nova corotina (1), que pode gerar uma exceção não tratada. Se você tentar doWork com um try/catch (2), o aplicativo ainda falhará.
Isso ocorre porque a falha de qualquer componente filho do trabalho leva à falha imediata de seus pais.
Uma maneira de evitar o erro é usar SupervisorJob (1).
A falha ou cancelamento da execução do componente filho não levará à falha do pai e não afetará outros componentes.
val job = SupervisorJob()
Nota : isso só funcionará se você iniciar explicitamente sua chamada de rotina assíncrona com SupervisorJob . Portanto, o código abaixo ainda travará seu aplicativo, porque o async é executado como parte da rotina principal (1).
val job = SupervisorJob() val scope = CoroutineScope(Dispatchers.Default + job) fun loadData() = scope.launch { try { async {
Outra maneira de evitar uma falha, que é mais preferível, é coroutineScope async no coroutineScope (1). Agora, quando ocorre uma exceção dentro da async , ela cancela todas as outras corotinas criadas nessa área, sem tocar na área externa. 2)
val job = SupervisorJob() val scope = CoroutineScope(Dispatchers.Default + job)
Além disso, você pode manipular exceções dentro do bloco async .
Use o gerenciador principal para rotinas principais
Se você precisar fazer um trabalho em segundo plano e atualizar a interface do usuário dentro da sua rotina principal, inicie-a usando o expedidor principal.
val scope = CoroutineScope(Dispatchers.Default)
No exemplo acima, iniciamos a corotina raiz usando o despachante CoroutineScope no CoroutineScope (1). Com essa abordagem, toda vez que precisarmos atualizar a interface do usuário, teremos que mudar de contexto (2).
Na maioria dos casos, é preferível criar o CoroutineScope imediatamente com o expedidor principal, o que levará à simplificação do código e à troca de contexto menos explícita.
val scope = CoroutineScope(Dispatchers.Main) fun login() = scope.launch { view.showLoading() withContext(Dispatcher.IO) { networkClient.login(...) } view.hideLoading() }
Evite usar assíncrono / espera desnecessário
Se você usar a função async e chamar imediatamente em await , deverá parar de fazer isso.
launch { val data = async(Dispatchers.Default) { }.await() }
Se você deseja alternar o contexto da corotina e suspender imediatamente a corotina pai, com withContext é a maneira mais preferível para isso.
launch { val data = withContext(Dispatchers.Default) { } }
Em termos de desempenho, esse não é um problema tão grande (mesmo considerando que o async cria uma nova corotina para o trabalho), mas o async semanticamente implica que você deseja executar várias corotinas em segundo plano e esperar apenas por elas.
Evitar cancelamento de trabalho
Se você precisar cancelar a rotina, não cancele o trabalho.
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()
O problema com o código acima é que, quando cancelamos o trabalho, o colocamos em um estado concluído . As corotinas lançadas como parte de um trabalho concluído não serão executadas (1).
Se você quiser desfazer todas as corotinas em uma área específica, poderá usar a função cancelChildren . Além disso, é uma boa prática fornecer a capacidade de cancelar trabalhos individuais (2).
class WorkManager { val job = SupervisorJob() val scope = CoroutineScope(Dispatchers.Default + job) fun doWork1(): Job = scope.launch { }
Evite gravar a função de pausa usando o despachante implícito
Não escreva a função de suspend , cuja execução dependerá do gerenciador de corotina específico.
suspend fun login(): Result { view.showLoading() val result = withContext(Dispatcher.IO) { someBlockingCall() } view.hideLoading() return result }
No exemplo acima, a função de login é uma função de suspensão e falhará se você a iniciar a partir de uma rotina que o expedidor principal não utilizará.
launch(Dispatcher.Main) {
CalledFromWrongThreadException: somente o encadeamento de origem que criou a hierarquia dos componentes do View pode acessá-los.
Crie sua função de suspensão para que possa ser executada a partir de qualquer gerenciador de rotinas.
suspend fun login(): Result = withContext(Dispatcher.Main) { view.showLoading() val result = withContext(Dispatcher.IO) { someBlockingCall() } view.hideLoading() return result }
Agora podemos chamar nossa função de login de qualquer despachante.
launch(Dispatcher.Main) {
Evite usar o escopo global
Se você usa o GlobalScope em qualquer lugar do seu aplicativo Android, deve parar de fazer isso.
GlobalScope.launch {
O escopo global é usado para iniciar corotinas de nível superior que são executadas ao longo da vida útil do aplicativo e não são canceladas antes do tempo.
O código do aplicativo geralmente deve usar o CoroutineScope específico do aplicativo, portanto, o uso assíncrono ou de inicialização no GlobalScope é altamente desencorajado.
No Android, a corotina pode ser facilmente limitada ao ciclo de vida de uma Atividade, Fragmento, Visualização ou ViewModel.
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 {