Swift Concurrency Series 03|Como entender Task, Task.detached e MainActor
Eles geralmente aparecem juntos, mas respondem respectivamente a três perguntas: "De onde vem a tarefa, quem está vinculado a ela e quem altera a IU?"
Quando entrei em contato com o Swift Concurrency pela primeira vez, fiquei confuso sobre Task, Task.detached e MainActor:
- Tudo relacionado ao assíncrono
- geralmente aparecem no mesmo trecho de código
- Tudo parece “deixe o código rodar em algum lugar”
Portanto, a maneira mais comum de aprender é memorizar definições. No entanto, se você confiar apenas nas definições para lembrar esses três conceitos, é fácil ficar confuso ao aprendê-los. Por se parecerem com conceitos semelhantes, na verdade respondem a três tipos de perguntas completamente diferentes.
Uma maneira mais prática de entender isso é:
Task: Como iniciar a missãoTask.detached: Quão estreitamente a tarefa está vinculada ao contexto atualMainActor: Em que semântica de isolamento esta lógica deve ser executada?
Basta dividir essas três dimensões e muita confusão desaparecerá imediatamente.
1. Task resolve: Como entro no processo assíncrono a partir daqui?
Os cenários de uso mais naturais para Task são:
O código atual não é
async, mas preciso entrar em um processo assíncrono agora.
Isso é muito comum no iOS:
- O retorno de chamada do clique do botão não é
async - O método proxy UIKit não é
async - Um determinado evento de ciclo de vida síncrono aciona repentinamente uma solicitação assíncrona
Por exemplo:
Button("刷新") {
Task {
await viewModel.reload()
}
}
O significado de Task aqui é muito claro: conectar um portal de interação síncrona ao mundo assíncrono.
Portanto, a chave para Task é “o limite da tarefa é criado”.
Uma vez escrito significa:
- Aqui começa um trabalho assíncrono com ciclo de vida independente
- pode ser cancelado
- Isso vai acabar em algum momento
- Suas consequências, em última análise, terão que ser enfrentadas por alguém
Isso também mostra que Task não pode ser entendido apenas como “uma camada pode ser usada para await”.
2. As características reais do Task comum: Geralmente continua um período de trabalho assíncrono no contexto atual
Uma situação comum é entender o Task comum de forma muito “independente”, pensando que assim que for criado não tem nada a ver com o contexto atual.
Uma compreensão mais precisa em engenharia deveria ser:
**Comum Task costuma ser uma tarefa que se estende do contexto atual. **
Isso significa que muitas vezes herda parte do ambiente atual, como:
- Semântica atual do ator
- Contexto da tarefa atual
- Certos cancelamentos de relacionamentos
- Tendências prioritárias atuais
Portanto, é mais como “entrar de forma assíncrona no topo da lógica atual” do que “criar um novo universo completamente fora de sintonia”.
É importante entender isso, porque você entenderá mais tarde do que exatamente o Task.detached está “se desligando”.
3. Task.detached é uma declaração independente mais forte
Muitos artigos descrevem o Task.detached como uma versão “mais avançada”, o que pode facilmente levar a preconceitos na prática.
É mais perigoso.
A razão é simples: no fundo, é menos provável que herde o contexto actual.
Ou seja, ao escrever:
Task.detached {
...
}
Na verdade, está expressando:
- Este trabalho não quer estar naturalmente ligado ao contexto atual
- Quero que seja mais independente
- Estou disposto a assumir mais responsabilidades de ciclo de vida e isolamento
Isso é razoável em alguns cenários, como:
- Faça um trabalho de limpeza em segundo plano que tenha pouco a ver com a página atual
- Realizar certas tarefas que obviamente exigem romper com a semântica atual do ator
- Certas estruturas ou camadas de infraestrutura exigem explicitamente tarefas independentes
Mas no negócio de páginas, o que a maioria das pessoas realmente deseja é algo mais gerenciável.
E o detached muitas vezes apenas dificulta o gerenciamento.
4. Task.detached é facilmente mal utilizado
Porque a primeira sensação que isso dá às pessoas é de “liberdade”.
Mas em sistemas concorrentes, o preço da liberdade é muitas vezes o excesso de responsabilidades.
Depois de usar o Task.detached, em breve você terá que responder a estas perguntas novamente:
-Quem é o dono agora?
- Deve continuar quando a página for destruída?
- Se a tarefa externa for cancelada, ela ainda será executada?
- Ele pode alterar diretamente o estado atual depois de voltar?
Se nenhuma dessas perguntas for respondida claramente, Task.detached geralmente acaba fugindo da responsabilidade da missão.
Portanto, meu princípio padrão é simples:
- Use
Taskcomum primeiro para camadas de página e ViewModel. - Considere
Task.detachedapenas se você souber muito claramente “por que é necessário romper com o contexto atual”
5. MainActor não é o conceito de “iniciar uma tarefa”.
Este é o ponto que precisa ser mais bem esclarecido.
Task e Task.detached discutem:
Como uma tarefa assíncrona é criada e quão fortemente ela está vinculada ao contexto atual.
MainActor discute:
Sob qual semântica de isolamento um determinado trecho de código deve ser executado.
Não é uma “versão do thread principal da tarefa” nem uma “tarefa usada especificamente para atualizar a IU”. Essencialmente, informa ao compilador e ao chamador:
- Esta lógica pertence ao domínio de isolamento do ator principal
- Está fortemente relacionado à UI
- Não pode ser modificado aleatoriamente em nenhum contexto simultâneo
Portanto, o foco do MainActor sempre foi “restrições”.
6. Os códigos relacionados à IU devem ser levados a sério MainActor
Uma situação comum é pensar: Enfim, no final das contas, apenas atribuímos um valor na página, então não deve ser tão sério.
O problema é que o estado da UI verdadeiramente complexo nunca recebe um valor apenas uma vez.
As páginas reais geralmente têm estas coisas coexistindo:
- estado de carregamento
- Listar dados
- Estado vazio
- prompt de erro
- Alguns botões locais estão desativados
Depois que esses valores forem gravados por vários resultados assíncronos em diferentes momentos, sem limites MainActor claros, o problema se acumulará lentamente em:
- A página pisca ocasionalmente
- Algumas atualizações de status estão em uma ordem estranha
- ViewModel alterna entre a lógica de segundo plano e a lógica da UI
Portanto, o valor de MainActor não é apenas para “evitar erros de thread”, mas também para estabelecer um limite de propriedade claro para os estados da UI.
7. Uma sequência de julgamento mais próxima do combate real
Se você não consegue descobrir qual conceito usar ao escrever código, primeiro pergunte-se na seguinte ordem:
1. Estou em um contexto não assíncrono e preciso entrar em um processo assíncrono?
Nesse caso, considere primeiro Task.
2. Eu realmente preciso que esta tarefa exista independentemente do contexto atual?
Considere Task.detached apenas se a resposta for muito clara.
Se apenas “parece mais livre”, geralmente não deve ser usado.
3. Este código de leitura e gravação da UI está fortemente relacionado ao status?
Nesse caso, você deve considerar seriamente o MainActor em vez de esperar que algo dê errado.
Essa sequência de julgamento é muito mais útil do que memorizar definições de API porque corresponde diretamente aos problemas reais enfrentados ao escrever código de negócios.
8. Como é o mau cheiro mais comum?
Os três tipos mais comuns de mau cheiro que vejo são:
1. Onde await não estiver disponível, embale primeiro um Task
Isso tornará as entradas de tarefas no projeto cada vez mais fragmentadas e, no final, ninguém saberá quem é o proprietário dessas tarefas.
2. Não entendo herança de contexto e sempre use Task.detached
Isto parece muito “independente”, mas na verdade muitas vezes apenas leva ainda mais longe o problema do ciclo de vida.
3. ViewModel é responsável pelo processamento em segundo plano e pelo write-back da IU, mas não há um limite MainActor claro
Esse tipo de código pode não necessariamente travar no curto prazo, mas é particularmente propenso a acumular erros de estado ocultos no longo prazo.
9. Conclusão: Eles não estão na mesma dimensão
Se eu tivesse que lembrar esses três conceitos em uma frase, diria o seguinte:
Task: agora quero criar uma tarefa assíncronaTask.detached: Quero criar uma tarefa assíncrona que seja mais independente e menos herdada do contexto atual.MainActor: Esta lógica deve ser executada dentro do limite de isolamento do ator principal
Eles respondem a diferentes perguntas em sistemas simultâneos:
- Onde começa a missão?
- Quão estreitamente a tarefa está vinculada ao contexto atual
- Quais estados devem ser isolados pelo ator principal
Enquanto essas três dimensões estiverem separadas, ao escrever o código Swift Concurrency posteriormente, “iniciar uma tarefa” e “retornar ao thread principal” não serão mais confundidos com a mesma coisa.
What to read next
Want more posts about Swift Concurrency?
Posts in the same category are usually the best next step for reading more on this topic.
View same categoryWant to keep following #Swift Concurrency?
Tags are useful for related tools, specific problems, and similar troubleshooting notes.
View same tagWant to explore another direction?
If you are not sure what to read next, return to the homepage and start from categories, topics, or latest updates.
Back home