260 votes

Comment écrire une méthode asynchrone avec un paramètre de sortie ?

Je veux écrire une méthode asynchrone avec un paramètre out, comme ceci :

public async void Method1()
{
    int op;
    int result = await GetDataTaskAsync(out op);
}

Comment puis-je faire cela dans GetDataTaskAsync ?

365voto

dcastro Points 17978

Vous ne pouvez pas avoir de méthodes asynchrones avec des paramètres ref ou out.

Lucian Wischik explique pourquoi cela n'est pas possible sur ce fil MSDN : http://social.msdn.microsoft.com/Forums/en-US/d2f48a52-e35a-4948-844d-828a1a6deb74/why-async-methods-cannot-have-ref-or-out-parameters (La publication d'origine n'est plus disponible en raison de la dégradation des liens, la dernière version archivée peut être trouvée ici.)

Quant à la raison pour laquelle les méthodes asynchrones ne prennent pas en charge les paramètres de type out par référence ? (ou les paramètres ref ?) C'est une limitation du CLR. Nous avons choisi de mettre en œuvre les méthodes asynchrones de manière similaire aux méthodes de générateur -- c'est-à-dire en transformant la méthode en un objet-machine-à-états par le biais du compilateur. Le CLR n'a pas de moyen sûr de stocker l'adresse d'un "paramètre out" ou "paramètre de référence" en tant que champ d'un objet. La seule façon d'avoir pris en charge les paramètres de type out par référence aurait été si la fonctionnalité asynchrone avait été réalisée par une réécriture du CLR de bas niveau au lieu d'une réécriture du compilateur. Nous avons examiné cette approche, et elle avait beaucoup de bons côtés, mais cela aurait finalement été si coûteux que cela ne se serait jamais produit.

Une solution habituelle pour cette situation est de faire en sorte que la méthode asynchrone renvoie un Tuple à la place. Vous pourriez réécrire votre méthode comme ceci :

public async Task Method1()
{
    var tuple = await GetDataTaskAsync();
    int op = tuple.Item1;
    int result = tuple.Item2;
}

public async Task> GetDataTaskAsync()
{
    //...
    return new Tuple(1, 2);
}

78voto

jv_ Points 490

La solution C#7+ consiste à utiliser une syntaxe de tuple implicite.

    private async Task<(bool IsSuccess, IActionResult Result)> TryLogin(OpenIdConnectRequest request)
    { 
        return (true, BadRequest(new OpenIdErrorResponse
        {
            Error = OpenIdConnectConstants.Errors.AccessDenied,
            ErrorDescription = "Access token provided is not valid."
        }));
    }

Le résultat de retour utilise les noms de propriétés définis dans la signature de la méthode. Par exemple :

var foo = await TryLogin(request);
if (foo.IsSuccess)
     return foo.Result;

61voto

Alex Points 10289

Vous ne pouvez pas avoir des paramètres ref ou out dans les méthodes async (comme cela a déjà été noté).

Cela appelle à faire un peu de modélisation dans les données qui circulent :

public class Data
{
    public int Op {get; set;}
    public int Result {get; set;}
}

public async void Method1()
{
    Data data = await GetDataTaskAsync();
    // utilisez data.Op et data.Result à partir de maintenant
}

public async Task GetDataTaskAsync()
{
    var returnValue = new Data();
    // Remplir la valeur de retour
    return returnValue;
}

Vous gagnez la capacité de réutiliser plus facilement votre code, en plus c'est beaucoup plus lisible que les variables ou les tuples.

21voto

Michael Gehling Points 370

J'avais le même problème car j'aime utiliser le modèle Try-method-pattern qui semble être fondamentalement incompatible avec le paradigme async-await...

Il est important pour moi de pouvoir appeler la méthode Try dans une seule clause if et de ne pas avoir à pré-définir les variables de sortie avant, mais de pouvoir le faire en ligne comme dans l'exemple suivant :

if (TryReceive(out string msg))
{
    // utiliser msg
}

J'ai donc trouvé les solutions suivantes :

Note : La nouvelle solution est supérieure, car elle peut être utilisée avec des méthodes qui retournent simplement un tuple comme décrit dans de nombreuses autres réponses ici, ce qui peut souvent être trouvé dans du code existant !

Nouvelle solution :

  1. Créer des méthodes d'extension pour les ValueTuples :

    public static class TupleExtensions
    {
      public static bool TryOut(this ValueTuple tuple, out P2 p2)
      {
         bool p1;
         (p1, p2) = tuple;
         return p1;
      }
    
      public static bool TryOut(this ValueTuple tuple, out P2 p2, out P3 p3)
      {
         bool p1;
         (p1, p2, p3) = tuple;
         return p1;
      }
    
      // continuer de supporter des tuples plus grands...
    }
  2. Définir une méthode async Try comme ceci :

     public async Task<(bool, string)> TryReceiveAsync()
     {
         string message;
         bool success;
         // ...
         return (success, message);
     }
  3. Appeler la méthode async Try comme ceci :

     if ((await TryReceiveAsync()).TryOut(out string msg))
     {
         // utiliser msg
     }

Ancienne solution :

  1. Définir une structure d'aide :

     public struct AsyncOut
     {
         private readonly T returnValue;
         private readonly OUT result;
    
         public AsyncOut(T returnValue, OUT result)
         {
             this.returnValue = returnValue;
             this.result = result;
         }
    
         public T Out(out OUT result)
         {
             result = this.result;
             return returnValue;
         }
    
         public T ReturnValue => returnValue;
    
         public static implicit operator AsyncOut((T returnValue ,OUT result) tuple) => 
             new AsyncOut(tuple.returnValue, tuple.result);
     }
  2. Définir une méthode async Try comme ceci :

     public async Task> TryReceiveAsync()
     {
         string message;
         bool success;
         // ...
         return (success, message);
     }
  3. Appeler la méthode async Try comme ceci :

     if ((await TryReceiveAsync()).Out(out string msg))
     {
         // utiliser msg
     }

Pour plusieurs paramètres de sortie, vous pouvez définir des structures supplémentaires (par exemple, AsyncOut) ou vous pouvez retourner un tuple.

15voto

Scott Turner Points 217

Alex a fait un excellent point sur la lisibilité. Équivalent, une fonction est également suffisamment d'interface pour définir le ou les types renvoyés et vous obtenez également des noms de variables significatifs.

delegate void OpDelegate(int op);
Task GetDataTaskAsync(OpDelegate callback)
{
    bool canGetData = true;
    if (canGetData) callback(5);
    return Task.FromResult(canGetData);
}

Les appelants fournissent une lambda (ou une fonction nommée) et l'intellisense aide en copiant le(s) nom(s) de variable depuis le délégué.

int myOp;
bool result = await GetDataTaskAsync(op => myOp = op);

Cette approche particulière est comme une méthode "Try" où myOp est défini si le résultat de la méthode est true. Sinon, vous ne vous souciez pas de myOp.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X