web-dev-qa-db-fra.com

Comment écrire une méthode asynchrone avec le paramètre out?

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 est-ce que je fais ceci dans GetDataTaskAsync?

140
jesse

Vous ne pouvez pas utiliser de méthodes asynchrones avec les 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

Pourquoi les méthodes asynchrones ne prennent-elles pas en charge les paramètres hors référence? (ou paramètres de référence?) C'est une limitation du CLR. Nous avons choisi d’implémenter les méthodes asynchrones de la même manière que les méthodes itérateurs, c’est-à-dire que le compilateur transforme la méthode en un objet machine à états. Le CLR ne dispose d'aucun moyen sûr de stocker l'adresse d'un "paramètre de sortie" ou d'un "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 hors référence serait si la fonctionnalité asynchrone était effectuée par une réécriture CLR de bas niveau au lieu d'une réécriture par le compilateur. Nous avons examiné cette approche et elle en avait beaucoup, mais au final, cela aurait été si coûteux que cela ne se serait jamais produit.

Une solution de contournement typique pour cette situation consiste à demander à la méthode asynchrone de renvoyer un tuple. Vous pouvez réécrire votre méthode en tant que telle:

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

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

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

Cela appelle une certaine modélisation dans les données en mouvement:

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

public async void Method1()
{
    Data data = await GetDataTaskAsync();
    // use data.Op and data.Result from here on
}

public async Task<Data> GetDataTaskAsync()
{
    var returnValue = new Data();
    // Fill up returnValue
    return returnValue;
}

Vous avez la possibilité de réutiliser votre code plus facilement, mais il est également plus lisible que les variables ou les n-uplets.

44
Alex

La solution C # 7 + consiste à utiliser la syntaxe implicite de Tuple.

    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 renvoyé utilise les noms de propriété définis par la signature de la méthode. par exemple:

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

Une caractéristique intéressante des paramètres out est qu'ils peuvent être utilisés pour renvoyer des données même lorsqu'une fonction lève une exception. Je pense que l'équivalent le plus proche de cette opération avec une méthode async serait d'utiliser un nouvel objet pour stocker les données auxquelles la méthode async et l'appelant peuvent se référer. Une autre façon serait de transmettre un délégué comme suggéré dans une autre réponse .

Notez qu'aucune de ces techniques n'aura le même type d'application du compilateur que out. En d’autres termes, le compilateur ne vous demandera pas de définir la valeur sur l’objet partagé ni d’appeler un délégué passé.

Voici un exemple d'implémentation utilisant un objet partagé pour imiter ref et out à utiliser avec les méthodes async et divers autres scénarios dans lesquels ref et out ne sont pas disponibles. :

class Ref<T>
{
    // Field rather than a property to support passing to functions
    // accepting `ref T` or `out T`.
    public T Value;
}

async Task OperationExampleAsync(Ref<int> successfulLoopsRef)
{
    var things = new[] { 0, 1, 2, };
    var i = 0;
    while (true)
    {
        // Fourth iteration will throw an exception, but we will still have
        // communicated data back to the caller via successfulLoopsRef.
        things[i] += i;
        successfulLoopsRef.Value++;
        i++;
    }
}

async Task UsageExample()
{
    var successCounterRef = new Ref<int>();
    // Note that it does not make sense to access successCounterRef
    // until OperationExampleAsync completes (either fails or succeeds)
    // because there’s no synchronization. Here, I think of passing
    // the variable as “temporarily giving ownership” of the referenced
    // object to OperationExampleAsync. Deciding on conventions is up to
    // you and belongs in documentation ^^.
    try
    {
        await OperationExampleAsync(successCounterRef);
    }
    finally
    {
        Console.WriteLine($"Had {successCounterRef.Value} successful loops.");
    }
}
8
binki

Alex a beaucoup insisté sur la lisibilité. De manière équivalente, une fonction est également une interface suffisante pour définir le (s) type (s) renvoyé (s) et vous obtenez également des noms de variables significatifs.

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

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

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

Cette approche particulière ressemble à 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.

8
Scott Turner

Je pense que l'utilisation de ValueTuples comme ceci peut fonctionner. Vous devez d'abord ajouter le paquet ValueTuple NuGet:

public async void Method1()
{
    (int op, int result) Tuple = await GetDataTaskAsync();
    int op = Tuple.op;
    int result = Tuple.result;
}

public async Task<(int op, int result)> GetDataTaskAsync()
{
    int x = 5;
    int y = 10;
    return (op: x, result: y):
}
2
Paul Marangoni

Voici le code de la réponse de @ dcastro modifiée en C # 7.0 avec les tuples nommés et la déconstruction de Tuple, ce qui simplifie la notation:

public async void Method1()
{
    // Version 1, named tuples:
    // just to show how it works
    /*
    var Tuple = await GetDataTaskAsync();
    int op = Tuple.paramOp;
    int result = Tuple.paramResult;
    */

    // Version 2, Tuple deconstruction:
    // much shorter, most elegant
    (int op, int result) = await GetDataTaskAsync();
}

public async Task<(int paramOp, int paramResult)> GetDataTaskAsync()
{
    //...
    return (1, 2);
}

Pour plus de détails sur les nouveaux tuples nommés, les littéraux Tuple et les déconstructions Tuple, voir: https://blogs.msdn.Microsoft.com/dotnet/2017/03/09/new-features-in-c-7-0 /

1
Jpsy

J'ai eu le même problème que j'aime utiliser le motif Try-method-pattern, qui semble fondamentalement incompatible avec le paradigme async-wait-wait ...

Ce qui est important pour moi, c'est que je peux appeler la méthode Try dans une seule clause if sans avoir à prédéfinir les variables de sortie auparavant, mais que je peux le faire en ligne, comme dans l'exemple suivant:

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

Alors je suis venu avec la solution suivante:

  1. Définir une structure d'aide:

    public struct AsyncOutResult<T, OUT>
    {
        T returnValue;
        OUT result;
    
        public AsyncOutResult(T returnValue, OUT result)
        {
            this.returnValue = returnValue;
            this.result = result;
        }
    
        public T Result(out OUT result)
        {
            result = this.result;
            return returnValue;
        }
    }
    
  2. Définissez async Try-method comme ceci:

    public async Task<AsyncOutResult<bool, string>> TryReceiveAsync()
    {
        string message;
        bool success;
        // ...
        return new AsyncOutResult<bool, string>(success, message);
    }
    
  3. Appelez la méthode try asynchrone comme ceci:

    if ((await TryReceiveAsync()).Result(out T msg))
    {
        // use msg
    }
    

Si vous avez besoin de plusieurs paramètres de sortie, vous pouvez bien sûr définir des structures supplémentaires, comme suit:

public struct AsyncOutResult<T, OUT1, OUT2>
{
    T returnValue;
    OUT1 result1;
    OUT2 result2;

    public AsyncOutResult(T returnValue, OUT1 result1, OUT2 result2)
    {
        this.returnValue = returnValue;
        this.result1 = result1;
        this.result2 = result2;
    }

    public T Result(out OUT1 result1, out OUT2 result2)
    {
        result1 = this.result1;
        result2 = this.result2;
        return returnValue;
    }
}
0
Michael Gehling