web-dev-qa-db-fra.com

Comment créez-vous une méthode asynchrone en C #?

Chaque article de blog que j'ai lu vous explique comment utiliser une méthode asynchrone en C #, mais pour une raison quelconque, n'expliquez jamais comment créer vos propres méthodes asynchrones. J'ai donc ce code en ce moment qui consomme ma méthode:

private async void button1_Click(object sender, EventArgs e)
{
    var now = await CountToAsync(1000);
    label1.Text = now.ToString();
}

Et j'ai écrit cette méthode qui est CountToAsync:

private Task<DateTime> CountToAsync(int num = 1000)
{
    return Task.Factory.StartNew(() =>
    {
        for (int i = 0; i < num; i++)
        {
            Console.WriteLine("#{0}", i);
        }
    }).ContinueWith(x => DateTime.Now);
}

Est-ce l'utilisation de Task.Factory, le meilleur moyen d'écrire une méthode asynchrone, ou devrais-je écrire ceci d'une autre manière?

180
Khalid Abuhakmeh

Je ne recommande pas StartNew à moins que vous n'ayez besoin de ce niveau de complexité.

Si votre méthode async dépend d'autres méthodes asynchrones, la méthode la plus simple consiste à utiliser le mot clé async:

private static async Task<DateTime> CountToAsync(int num = 10)
{
  for (int i = 0; i < num; i++)
  {
    await Task.Delay(TimeSpan.FromSeconds(1));
  }

  return DateTime.Now;
}

Si votre méthode asynchrone fonctionne avec le processeur, vous devez utiliser Task.Run:

private static async Task<DateTime> CountToAsync(int num = 10)
{
  await Task.Run(() => ...);
  return DateTime.Now;
}

Vous pouvez trouver mon async/await intro utile.

213
Stephen Cleary

Si vous ne voulez pas utiliser async/wait dans votre méthode, mais que vous le "décorez" de manière à pouvoir utiliser le mot-clé wait de l'extérieur, TaskCompletionSource.cs :

public static Task<T> RunAsync<T>(Func<T> function)
{ 
    if (function == null) throw new ArgumentNullException(“function”); 
    var tcs = new TaskCompletionSource<T>(); 
    ThreadPool.QueueUserWorkItem(_ =>          
    { 
        try 
        {  
           T result = function(); 
           tcs.SetResult(result);  
        } 
        catch(Exception exc) { tcs.SetException(exc); } 
   }); 
   return tcs.Task; 
}

à partir d'ici et ici

Pour prendre en charge un tel paradigme avec les tâches, nous avons besoin d’un moyen de conserver la façade de la tâche et de pouvoir faire référence à une opération asynchrone arbitraire en tant que tâche, tout en contrôlant la durée de vie de cette tâche en fonction des règles de l’infrastructure sous-jacente qui fournit la asynchronisme, et de le faire d'une manière qui ne coûte pas beaucoup. C'est le but de TaskCompletionSource.

J'ai vu est également utilisé dans la source .NET par exemple. WebClient.cs :

    [HostProtection(ExternalThreading = true)]
    [ComVisible(false)]
    public Task<string> UploadStringTaskAsync(Uri address, string method, string data)
    {
        // Create the task to be returned
        var tcs = new TaskCompletionSource<string>(address);

        // Setup the callback event handler
        UploadStringCompletedEventHandler handler = null;
        handler = (sender, e) => HandleCompletion(tcs, e, (args) => args.Result, handler, (webClient, completion) => webClient.UploadStringCompleted -= completion);
        this.UploadStringCompleted += handler;

        // Start the async operation.
        try { this.UploadStringAsync(address, method, data, tcs); }
        catch
        {
            this.UploadStringCompleted -= handler;
            throw;
        }

        // Return the task that represents the async operation
        return tcs.Task;
    }

Enfin, j'ai trouvé utile également ce qui suit:

On me pose cette question tout le temps. L’implication est qu’il doit exister un fil quelque part bloquant l’appel d’E/S vers la ressource externe. Le code asynchrone libère donc le thread de requête, mais uniquement au détriment d'un autre thread situé ailleurs dans le système, n'est-ce pas? Non pas du tout. Pour comprendre pourquoi les demandes asynchrones s’échelonnent, je vais tracer un exemple (simplifié) d’un appel d’E/S asynchrone. Disons qu'une requête doit écrire dans un fichier. Le thread de demande appelle la méthode d'écriture asynchrone. WriteAsync est implémenté par la bibliothèque de classes de base (BCL) et utilise des ports de complétion pour ses E/S asynchrones. Ainsi, l'appel WriteAsync est transmis au système d'exploitation sous forme d'écriture de fichier asynchrone. Le système d'exploitation communique ensuite avec la pile de pilotes en transmettant les données pour écrire dans un paquet de demande d'E/S (IRP). C’est là que les choses deviennent intéressantes: si un pilote de périphérique ne peut pas gérer immédiatement un IRP, il doit le gérer de manière asynchrone. Le pilote demande donc au disque de commencer à écrire et renvoie une réponse "en attente" au système d'exploitation. Le système d'exploitation transmet cette réponse "en attente" à la BCL, laquelle renvoie une tâche incomplète au code de traitement de la demande. Le code de traitement des demandes attend la tâche, qui renvoie une tâche incomplète à partir de cette méthode, etc. Enfin, le code de traitement de la demande renvoie finalement une tâche incomplète à ASP.NET et le thread de demande est libéré pour retourner dans le pool de threads.

Introduction à Async/Await sur ASP.NET

Si l'objectif est d'améliorer l'évolutivité (plutôt que la réactivité), tout repose sur l'existence d'une E/S externe qui offre la possibilité de le faire.

11
Alberto