Programmation C sharp/Fonctions asynchrones

L'appel à une fonction se fait de manière synchrone : aucune autre instruction n'est exécutée avant que la fonction ne retourne une valeur. Cependant certaines fonctions prennent beaucoup de temps, en particulier les opérations d'entrées-sorties, et les fonctions de communication par réseau informatique.

Programmation C#
Programmation C#
Modifier ce modèle

Pour ce genre de fonction, l'API .Net possède souvent deux versions de la méthode :

  • Une méthode synchrone qui attend la fin de l'opération avant d'en retourner le résultat,
  • Une méthode asynchrone demandant au pool de threads standard d'effectuer l'opération, puis retourne immédiatement à la méthode appelante.

Syntaxe et utilisation des méthodes asynchrones

modifier

Une méthode synchrone est déclarée selon la syntaxe suivante :

type_retour nom_methode ( arguments... )

La version asynchrone utilise deux autres méthodes. La première commence par Begin et demande au pool de threads standard d'effectuer l'opération. Une fois l'opération terminée, un delegate est appelé. Celui-ci appelle alors la méthode dont le nom commence par End pour récupérer la valeur retournée par la méthode asynchrone.

Ces deux méthodes ont la syntaxe suivante :

IAsyncResult Beginnom_methode ( arguments_non_ref...,
     AsyncCallback callback, object state )

type_retour Endnom_methode ( IAsyncResult, arguments_ref )

Les arguments de la méthode synchrone se retrouvent répartis entre les deux méthodes :

La méthode Begin possède deux arguments supplémentaires :

  • Un delegate de type AsyncCallback appelé quand l'opération est terminée (celui-ci doit alors appeler la méthode End correspondante),
  • Un objet à transmettre au delegate.

Le delegate AsyncCallback

modifier

La syntaxe du delegate AsyncCallback est la suivante :

public delegate void AsyncCallback(IAsyncResult result)

Le paramètre result est une interface de type IAsyncResult correspondant à celui retourné par la méthode Begin, et doit être passé à la méthode End.

L'interface IAsyncResult

modifier

L'interface IAsyncResult possède les propriétés en lecture seule suivantes :

  • Propriété généralement utilisée par le delegué appelé quand l'opération est terminée :
    object AsyncState
    Cet objet correspond à celui transmis à la méthode Begin.
  • Propriétés généralement utilisées par le code appelant la méthode Begin de l'opération asynchrone :
    WaitHandle AsyncWaitHandle
    Cet objet de synchronisation est mis dans l'état signalé quand l'opération est terminée.
    bool IsCompleted
    Ce booléen vaut true (vrai) lorsque l'opération est terminée.
    bool CompletedSynchronously
    Ce booléen vaut true (vrai) si l'opération s'est terminée de manière synchrone.

La propriété AsyncWaitHandle permet de lancer une opération asynchrone, d'effectuer d'autres traitements durant l'opération en cours, et finalement attendre la fin de l'opération si elle ne s'est pas déjà terminée, en testant la propriété IsCompleted.

Fonctions asynchrones et tâches

modifier

Depuis C#5.0, il est possible de simplifier la programmation asynchrone avec les mots-clés async et await.

Déclaration d'une fonction asynchrone

modifier

Le mot-clé async est utilisé dans la déclaration d'une méthode pour indiquer qu'elle est asynchrone. Cela signifie que son exécution comporte une longue tâche, et qu'elle peut utiliser le mot-clé await pour attendre une autre tâche. Le type de sa valeur de retour T est modifié en Task<T> indiquant que la fonction asynchrone retourne désormais une tâche dont le résultat est de type T.

Par convention, le nom d'une fonction asynchrone se termine par "Async".

Exemple :

async Task<string> getPageContentAsync(string url)
{
    string page_content = null;
    // ... connexion, téléchargement, ...
    // page_content = ...
    return page_content;
}

Appeler et attendre une tâche

modifier

Appeler une fonction asynchrone est une façon de créer une nouvelle tâche :

async void test()
{
    Console.WriteLine("Téléchargement des pages...");
    Task<string> tache = getPageContentAsync("https://fr.wikibooks.org/w/Programmation_C_sharp/Fonctions_asynchrones");
    Console.WriteLine("Le programme peut faire autre chose pendant le téléchargement...");

    Console.WriteLine("Là on attend pour utiliser le résultat :");
    string page = await tache;
    Console.WriteLine("La page contient : ");
    Console.WriteLine(page);
}

L'appel de l'exemple précédent se fait de manière asynchrone, récupérant un objet tâche. La méthode peut faire autre chose avant d'attendre et récupérer le résultat explicitement en utilisant await. L'utilisation du mot-clé await dans une méthode n'est autorisé que si la méthode est déclarée elle-même asynchrone avec async.

La hiérarchie des appels est donc une chaîne de méthodes asynchrones (async -> attendre avec await, donc méthode async, ...), qui peut toutefois s'arrêter soit en n'utilisant pas le mot-clé await si on n'a pas besoin d'attendre la fin, soit en utilisant la méthode Wait() de la classe Task comme dans cet exemple :

void test()
{
    Console.WriteLine("Téléchargement des pages...");
    Task<string> tache = getPageContentAsync("https://fr.wikibooks.org/w/Programmation_C_sharp/Fonctions_asynchrones");
    Console.WriteLine("Le programme peut faire autre chose pendant le téléchargement...");

    Console.WriteLine("Là on attend :");
    tache.Wait();
    Console.WriteLine("Fin de tâche");
}

Créer une tâche

modifier

La classe Task possède des méthodes statiques pour créer des tâches :

  • La méthode Task.Run( function() ) crée une tâche pour exécuter la fonction donnée en argument.
  • La méthode Task.Wait( délai_en_millisecondes ) crée une tâche pour attendre le nombre de millisecondes spécifié.

Exemple pour simuler le téléchargement de page des exemples précédents :

async Task<string> getPageContentAsync(string url)
{
    string page_content = "<<LOADING...>>";
    // Utilisation de la notation   (arguments) => {code}
    await Task.Run(() =>  
        {  
            // ... connexion, téléchargement, ...
            Console.WriteLine("* Loading...");
            Task.Delay(2000).Wait();
            page_content = "<<Contenu de la page "+url+">>";
            Console.WriteLine("* Loaded");
        });  
    Console.WriteLine("* Return");
    return page_content;
}