Async/ En attente (concurrence moderne dans Swift)

Asynchrone/Attente dans Swift (concurrence moderne)
Asynchrone/Attente
Qu’est-ce que l’asynchrone/en attente ?
Apple introduit une nouvelle fonctionnalité de concurrence, à savoir asynchrone (async) à partir de Swift 5.5.
Asynchrone:- signifie asynchrone, ce qui signifie exécuter les méthodes/programmes de manière asynchrone.
Attendre:- est le mot clé à utiliser pour appeler les méthodes asynchrones. Attendre attend le résultat. Cela indique que votre programme peut interrompre son exécution en attendant le résultat.
Plate-forme prise en charge :
- Version rapide:- Rapide 5.5
- Version iOS: – iOS 15 et versions ultérieures
- Rétrocompatibilité: – iOS 13, Xcode 13.2, macOS 10.15, watchOS 6 et tvOS 13. Cette rétrocompatibilité s’applique uniquement aux fonctionnalités du langage Swift ; vous ne pouvez pas utiliser les API construites à l’aide de ces fonctionnalités de langage, comme le nouveau URLSession API qui utilisent async/wait (pour cela, vous avez toujours besoin d’iOS 15.0).
Comment créer et appeler une fonction asynchrone ?
La fonction ci-dessous a récupéré la vignette et renvoyé UIImage et String dans un tuple. Dans ce nouveau type de fonction asynchrone (async), nous ajoutons simplement un asynchrone mot-clé avant le type de retour. Après cela, créez une demande d’URL, récupérez les données de cette demande et créez une vignette.
Voici l’extension de UIImage pour générer une vignette. byPreparingThumbnail est une méthode asynchrone, c’est pourquoi elle marque comme await.
Cette méthode est marquée comme un lancer asynchrone ce qui signifie qu’il s’exécute de manière asynchrone et génère une erreur, le cas échéant.
Lors de l’appel d’une fonction asynchrone, nous devons utiliser await devant l’appel pour marquer d’éventuels points de suspension. Lorsque nous demandons de télécharger une image, le code asynchrone suspend et libère le thread et le transmet au système pour effectuer une autre tâche importante/concurrente. Et quand il reprend plus tard, il s’exécute plus loin sur ce même thread.
Appelez la fonction asynchrone en série :
Dans le code ci-dessus, toutes les images sont téléchargées en série. Nous disons aux applications qui attendent que la première image soit renvoyée jusqu’à ce qu’elles puissent continuer à récupérer une deuxième image, et ainsi de suite. Toutes les images sont en téléchargement dans l’ordre. Après cela, nous créons un tableau de toutes les images téléchargées.
Laissez asynchrone : appeler la fonction asynchrone en parallèle.
Lorsque l’on souhaite télécharger des images en parallèle, elles n’ont pas de fonctionnement dépendant. Nous avons juste besoin de mettre un mot-clé asynchrone avant le let. Notre tableau d’images doit maintenant être défini à l’aide de la attendre mot clé car nous avons affaire à des constantes asynchrones.
Inconvénient de l’approche du gestionnaire d’achèvement :
- Il est possible que les fonctions appellent leur gestionnaire d’achèvement plus d’une fois, ou qu’elles oublient de l’appeler entièrement.
- Dans le code ci-dessus, il existe cinq possibilités de bogues subtils si nous ne connaissons pas ou avons oublié le gestionnaire d’achèvement
- La syntaxe des paramètres @escaping (Chaîne) -> Vide peut être difficile à lire.
- Il était difficile de renvoyer des erreurs avec le gestionnaire d’achèvement (jusqu’à ce que Swift 5.0 ajoute le type de résultat Result
) - Explosion de fil: L’approche du gestionnaire d’achèvement bloque le thread et le libère lorsque la fonction renvoie le résultat. Ainsi, lorsque le système crée plusieurs threads, puis que le système est surchargé, fil de discussion explosion se produit.
Avantage de l’approche asynchrone en attente :
- Évitez le problème Pyramid of Doom avec des fermetures imbriquées
- Réduction de code
- Plus facile à lire
- Sécurité. Avec async/wait, un résultat est garanti, tandis que les blocs de complétion peuvent ou non être appelés.
- Il ne bloque pas le fil. Async a suspendu l’appel de la fonction et a libéré le thread pour d’autres travaux importants (qui sont décidés par le système). L’approche du gestionnaire d’achèvement bloque le thread et le libère lorsque la fonction renvoie le résultat
- Pas d’explosion de fil
- Pool de threads coopératif: – Éviter l’explosion des threads et les changements de contexte excessifs. Il est conçu de manière à réutiliser les fils idéaux au lieu de les bloquer.
Voici quelques points importants à retenir sur async/wait.
- Tout d’abord, lorsque vous marquez une fonction comme asynchrone, vous l’autorisez à se suspendre. Et lorsqu’une fonction se suspend elle-même, elle suspend également ses appelants. Ses appelants doivent donc également être asynchrones.
- attendre signifie que la fonction asynchrone peut suspendre l’exécution une ou plusieurs fois
- lorsqu’une fonction asynchrone est utilisée, le thread n’est pas bloqué
TÂCHE
Tâche: La tâche est une unité de travail asynchrone. Chaque fonction/méthode asynchrone est exécutée dans une tâche.
Comment créer et exécuter une tâche ?
Exécution d’une méthode asynchrone dans une tâche
Comme vous le voyez ci-dessus, lorsque nous appelons laisser premièreimage = essayer? attendre fetchThumbnail(for: « 23 ») nous avons eu une erreur du compilateur Ie « Appel ‘async’ dans une fonction qui ne prend pas en charge la concurrence »
Qu’est-ce que cela signifie?
De WWDC21 Le compilateur Swift nous dit que nous ne pouvons pas appeler des fonctions asynchrones dans des contextes qui ne sont pas eux-mêmes asynchrones. lorsque vous avez essayé d’appeler une fonction asynchrone à partir d’un environnement synchrone, ce qui n’est pas autorisé. Les fonctions asynchrones doivent pouvoir se suspendre elles-mêmes et leurs appelants, et les fonctions synchrones ne savent pas comment faire cela.
Alors, quelle est la solution à cette erreur ? La solution est une Tâche!
La tâche asynchrone regroupe le travail dans la fermeture et l’envoie au système pour une exécution immédiate sur le prochain thread disponible, comme une fonction asynchrone sur une file d’attente de répartition globale.
Une autre façon de résoudre cette erreur est, Nous pouvons marquer la fonction async mais en utilisant this; nous aurons une erreur dans la hiérarchie supérieure
Comme quelque part quand nous appelons la méthode fetchThumbnail(), cela donne la même erreur « Appel ‘async’ dans une fonction qui ne prend pas en charge la simultanéité. » Donc, pour résoudre cela à nouveau, nous devons utiliser la tâche
La tâche a plus de fonctionnalités comme :
1. Priorité des tâches: Nous pouvons décider de la priorité des tâches comme l’arrière-plan, l’utilitaire, etc., mais la priorité peut également être nulle si aucune priorité n’est attribuée comme nous l’avons fait ci-dessus.
2. Annuler: – Annulation d’une tâche et de sa tâche enfant : l’annulation d’une tâche parent annule automatiquement toutes ses tâches enfant.
3.Task.currentPriority : pour vérifier la priorité des tâches
Groupe de travail
Groupe de tâches : GROUPE DE TÂCHES Aidez-nous à exécuter les tâches simultanées et parallèles et à regrouper son résultat pour le renvoyer comme sortie finale (unique)
Création d’une tâche : La tâche se décline en deux saveurs.
- En lançant une erreur
2. Sans générer d’erreur :
Pour créer un groupe de tâches, nous pouvons utiliser avec groupe de tâches ou withThrowingTaskGroup selon nos besoins. Pour l’instant, nous allons de l’avant avec TaskGroup. Donc, notre code ressemble à ci-dessous :
Dans cet exemple, nous allons créer un groupe de tâches comportant plusieurs tâches enfants qui exécutent une UserOperation et renvoient son résultat. Lorsque toutes les opérations sont terminées, le groupe de tâches collecte tous les résultats des tâches enfants et les renvoie dans un dictionnaire.
Supposons que nous ayons un tableau d’utilisateurs, comme indiqué ci-dessous :
Nous en renvoyons quelques-unes pour la tâche enfant, et le groupe de tâches renvoie un dictionnaire contenant le nom complet et les initiales de l’utilisateur.
Comme vous pouvez le voir, nous avons bouclé toutes les opérations et ajouté la tâche enfant au groupe pour traiter le nom complet et l’initiale. Toutes les tâches enfants s’exécutent simultanément et il n’y a aucun contrôle sur leur fin. Pour collecter le résultat de toutes les tâches enfants, nous avons un groupe de tâches en boucle. Comme nous le voyons ci-dessus, le mot clé await en boucle indique que la boucle for peut s’interrompre en attendant la fin d’une tâche enfant. Chaque fois que la tâche enfant renvoie le résultat, la boucle for itère et met à jour le dictionnaire, et lorsque tous les enfants sont terminés, le groupe for-in se ferme et renvoie le résultat du groupe.
Lorsque nous l’exécutons, nous obtenons le résultat suivant :
Comme vous le voyez, l’opération de résultat est terminée en 10 sec. Et toutes les tâches enfants s’exécutent simultanément et le groupe de tâches ne revient que lorsque toutes les tâches enfants sont terminées. Cela indique également que l’enfant n’est disponible que dans un contexte de groupe de tâches.
Acteur de cinéma
La Acteur de cinéma est utilisé pour protéger l’état mutable dans Swift. Cela nous aide à protéger les courses de données.
Les courses aux données se produisent lorsque :
- Deux threads accèdent simultanément aux mêmes données
- Et chacun d’eux modifie les mêmes données
L’acteur est similaire à la classe dans la plupart des cas. Certains points sont discutés ci-dessous :
- Les acteurs sont de type référence
- L’acteur a des propriétés, des méthodes, des initialiseurs, un désinitialisateur et des indices
- Ne prend pas en charge l’héritage.
- Peut exécuter une méthode à la fois
- Il confirme automatiquement le protocole Actor.
Voici une vieille chose qui peut remplacer par Acteur de cinéma :
- DispatchQueue.main.async utilisation Acteur principal
- DispatchBarrier/Lock utilisation Acteur de cinéma
- Course aux données utilisation Acteur de cinéma
Passons à l’ancien, comment pouvons-nous éviter les courses de données ? Nous avons utilisé Dispatch Barrier ou lock pour sauver notre programme de DataRaces.
Ici, nous créons un petit module bancaire dans lequel nous pouvons déposer, retirer et vérifier le solde bancaire. Dans cet exemple, nous créons une file d’attente de barrière pour empêcher les courses de données lors de l’accès mutable disponibleSolde variables. Dans toutes les fonctions, nous avons utilisé un indicateur de barrière pour autoriser l’accès à un thread à la fois.
Nous avons maintenant créé une file d’attente pour retirer et déposer des montants de manière asynchrone dans notre banque.
Au fur et à mesure que nous exécutons cela, nous pouvons voir que le solde initial de la banque est de 100, et lorsque le solde total après retrait et dépôt est de 90. Cela semble parfaitement correct car il s’agit d’une opération asynchrone. Il est possible de retirer avant le dépôt et vice versa.
Recommençons maintenant en utilisant Acteur de cinéma. Nous avons changé Class en Actor ici et supprimé la file d’attente de barrière, qui est utilisée dans les exemples de classe. Un acteur a sérialisé toutes ses propriétés, garantissant qu’une seule interaction se produit à la fois, nous offrant une protection complète contre les courses de données car toutes les mutations sont effectuées en série. Ainsi, dans l’acteur Banque, nous avons un solde disponible modifiable, et il y accède en série ; par conséquent, il protège contre les courses de données.
Pour prendre en charge la simultanéité, nous masquons simplement l’attente et la lions à la tâche, comme expliqué dans la section Attente asynchrone et tâche.
@MainActor : le principal L’acteur est un acteur unique au monde qui exécute des tâches sur le thread principal.
Plus tôt, tout ce dont nous avons besoin pour mettre à jour l’interface utilisateur dans le fil principal que nous devons utiliser dispatchQueue.main.async, mais en utilisant le @mainActor, nous n’en avons pas besoin pour mettre à jour l’interface utilisateur dans le thread principal. Dans notre exemple de banque, il y a une étiquette sur l’interface utilisateur sur laquelle nous allons afficher le solde. Donc, pour mettre à jour l’interface utilisateur sur le thread principal, nous devons marquer la fonction showBalance avec @acteur principal attribut.
L’acteur principal est un acteur global que nous pouvons utiliser avec des propriétés, des méthodes, des fermetures et des instances. Par exemple, nous avons ajouté un attribut @acteur principal avant la déclaration de la fonction, ce qui signifie que la fonction sera exécutée dans le thread principal.
Conclusion : C’est tout pour le moment. Merci d’avoir lu. Dans la concurrence moderne, il reste plus de concepts, comme la concurrence de structure, l’isolement d’acteur, le saut d’acteur, etc. mais il n’est pas possible de couvrir toutes ces sections, vous pouvez également vérifier cela dans WWDC21 et WWDC22.
Source link