Programmation asynchrone en Rust | Opensource.com


  • Français


  • Programmation asynchrone : Incroyablement utile mais difficile à apprendre. Vous ne pouvez pas éviter la programmation asynchrone pour créer une application rapide et réactive. Les applications avec une grande quantité d’E/S de fichiers ou de réseau ou avec une interface graphique qui doit toujours être réactive bénéficient énormément de la programmation asynchrone. Les tâches peuvent être exécutées en arrière-plan pendant que l’utilisateur fait encore des entrées. La programmation asynchrone est possible dans de nombreux langages, chacun avec des styles et une syntaxe différents. La rouille ne fait pas exception. Dans Rust, cette fonctionnalité s’appelle attente asynchrone.

    Alors que attente asynchrone fait partie intégrante de Rust depuis la version 1.39.0, la plupart des applications dépendent des caisses communautaires. Dans Rust, à l’exception d’un binaire plus grand, attente asynchrone vient sans frais. Cet article vous donne un aperçu de la programmation asynchrone dans Rust.

    Sous la capuche

    Pour avoir une compréhension de base de attente asynchrone à Rust, vous commencez littéralement par le milieu.

    Le centre de attente asynchrone est le avenir trait, qui déclare la méthode sondage (Je couvre cela plus en détail ci-dessous). Si une valeur peut être calculée de manière asynchrone, le type associé doit implémenter le avenir caractéristique. La sondage La méthode est appelée à plusieurs reprises jusqu’à ce que la valeur finale soit disponible.

    À ce stade, vous pouvez appeler à plusieurs reprises le sondage méthode de votre application synchrone manuellement afin d’obtenir la valeur finale. Cependant, puisque je parle de programmation asynchrone, vous pouvez confier cette tâche à un autre composant : le runtime. Donc, avant de pouvoir utiliser le asynchrone syntaxe, un runtime doit être présent. J’utilise le runtime de Tokyo caisse communautaire dans les exemples suivants.

    Un moyen pratique de rendre le runtime tokio disponible est d’utiliser le #[tokio::main] macro sur votre fonction principale :

    #[tokio::main]
    async fn main(){
        println!("Start!");
        sleep(Duration::from_secs(1)).await;
        println!("End after 1 second");
    }

    Lorsque le runtime est disponible, vous pouvez maintenant attendre futurs. En attente signifie que d’autres exécutions s’arrêtent ici tant que le avenir doit être complété. La attendre méthode oblige le runtime à invoquer la sondage méthode, qui conduira la avenir À l’achèvement.

    Dans l’exemple ci-dessus, les tokios dormir la fonction renvoie un avenir qui se termine lorsque la durée spécifiée s’est écoulée. En attendant cet avenir, la relation sondage méthode est appelée à plusieurs reprises jusqu’à ce que la avenir complète. De plus, le principale() la fonction renvoie également un avenir en raison de l async mot-clé avant le fn.

    Donc, si vous voyez une fonction marquée avec async:

    async fn foo() -> usize { /**/ }

    Alors c’est juste du sucre syntaxique pour :

    fn foo() -> impl Future<Output = usize> { async { /**/ } }

    Épingler et boxer

    Pour enlever certains des linceuls et des nuages ​​de attente asynchrone à Rust, vous devez comprendre épingler et boxe.

    Si vous avez affaire à attente asynchrone, vous enjamberez assez rapidement les termes boxing et pinning. Comme je trouve que les explications disponibles sur le sujet sont assez difficiles à comprendre, je me suis fixé comme objectif d’expliquer plus facilement le problème.

    Parfois, il est nécessaire d’avoir des objets qui sont garantis de ne pas être déplacés en mémoire. Cela entre en vigueur lorsque vous avez un type auto-référentiel :

    struct MustBePinned {
        a: int16,
        b: &int16
    }

    Si membre b est une référence (pointeur) au membre un de la même instance, puis référencez b devient invalide lorsque l’instance est déplacée car l’emplacement du membre un a changé mais b pointe toujours vers l’emplacement précédent. Vous pouvez trouver un exemple plus complet d’un autoréférentiel tapez dans le Livre Rust Async. Tout ce que vous devez savoir maintenant, c’est qu’une instance de Doit être épinglé ne doit pas être déplacé en mémoire. Genre comme Doit être épinglé ne met pas en œuvre le Détacher trait, ce qui leur permettrait de se déplacer dans la mémoire en toute sécurité. Autrement dit, Doit être épinglé est !Détacher.

    Retour vers le futur : Par défaut, un avenir est aussi !Détacher; ainsi, il ne doit pas être déplacé en mémoire. Alors, comment gérez-vous ces types? Vous les épinglez et les encadrez.

    La Broche type enveloppe les types de pointeur, garantissant que les valeurs derrière le pointeur ne seront pas déplacées. La Broche type garantit cela en ne fournissant pas de référence mutable du type encapsulé. Le type sera épinglé pour la durée de vie de l’objet. Si vous épinglez accidentellement un type qui implémente Détacher (qui peut être déplacé en toute sécurité), cela n’aura aucun effet.

    En pratique : Si vous souhaitez restituer un avenir (!Détacher) à partir d’une fonction, vous devez l’encadrer. Utilisant Boîte provoque l’allocation du type sur le tas au lieu de la pile et garantit ainsi qu’il peut survivre à la fonction actuelle sans être déplacé. En particulier, si vous souhaitez remettre un avenirvous ne pouvez lui donner qu’un pointeur en tant que avenir doit être de type Pin>.

    Utilisant attente asynchrone, vous tomberez certainement sur cette syntaxe de boxe et d’épinglage. Pour conclure ce sujet, il suffit de retenir ceci :

    • Rust ne sait pas si un type peut être déplacé en toute sécurité.
    • Les types qui ne doivent pas être déplacés doivent être enveloppés à l’intérieur Broche.
    • La plupart des types sont des types non épinglés. Ils mettent en œuvre le trait Détacher et peut être déplacé librement dans la mémoire.
    • Si un type est enveloppé à l’intérieur Broche et le type enveloppé est !Détacheril n’est pas possible d’en tirer une référence mutable.
    • Les contrats à terme créés par le asynchrone mot-clé sont !Détacher et doit donc être épinglé.

    Caractère futur

    Dans le avenir trait, tout s’enchaîne :

    pub trait Future {
        type Output;

        fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
    }

    Voici un exemple simple de mise en œuvre de la avenir caractéristique:

    struct  MyCounterFuture {
            cnt : u32,
            cnt_final : u32
    }

    impl MyCounterFuture {
            pub fn new(final_value : u32) -> Self {
                    Self {
                            cnt : 0,
                            cnt_final : final_value
                    }
            }
    }
     
    impl Future for MyCounterFuture {
            type Output = u32;

            fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<u32>{
                    self.cnt += 1;
                    if self.cnt >= self.cnt_final {
                            println!("Counting finished");
                            return Poll::Ready(self.cnt_final);
                    }

                    cx.waker().wake_by_ref();
                    Poll::Pending
            }
    }

    #[tokio::main]
    async fn main(){
            let my_counter = MyCounterFuture::new(42);

            let final_value = my_counter.await;
            println!("Final value: {}", final_value);
    }

    Voici un exemple simple de la façon dont le avenir trait est implémenté manuellement : Le avenir est initialisé avec une valeur à laquelle il doit compter, stocké dans cnt_final. A chaque fois le sondage méthode est invoquée, la valeur interne cent est incrémenté de un. Si cent est inférieur à cnt_finall’avenir signale le réveil du temps d’exécution que le avenir est prêt à être interrogé à nouveau. La valeur de retour de Poll::Pending signale que le avenir n’a pas encore terminé. Après cent est >= cnt_finalla sondage la fonction revient avec Poll::Readysignalant que le avenir a terminé et en fournissant la valeur finale.

    Ceci est juste un exemple simple, et bien sûr, il y a d’autres choses à prendre en compte. Si vous envisagez de créer votre propre avenir, je vous suggère fortement de lire le chapitre Asynchrone en profondeur dans la documentation du tokio crate.

    Emballer

    Avant de conclure, voici quelques informations supplémentaires que je considère utiles :

    • Créez un nouveau type épinglé et encadré à l’aide de Boîte :: goupille.
    • La contrats à terme caisse fournit le type BoîteAvenir qui vous permet de définir un avenir comme type de retour d’une fonction.
    • La async_trait permet de définir un asynchrone fonction dans les traits (ce qui n’est actuellement pas autorisé).
    • La pin-utils crate fournit des macros pour épingler les valeurs.
    • Les tokios try_join ! macro (a) attend plusieurs contrats à terme qui renvoient un Résultat.

    Une fois les premiers obstacles surmontés, la programmation asynchrone dans Rust est simple. Vous n’avez même pas besoin d’implémenter le avenir trait dans vos propres types si vous pouvez externaliser le code qui peut être exécuté en parallèle dans une fonction asynchrone. Dans Rust, des runtimes mono-thread et multi-thread sont disponibles, vous pouvez donc bénéficier de la programmation asynchrone même dans des environnements embarqués.

    Source

    Houssen Moshinaly

    Pour contacter personnellement le taulier :

    Laisser un commentaire

    Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

    Copy code