Comment implémentez-vous une méthode de délégué d'action asynchrone?

132

Un peu d'informations générales.

J'apprends la pile API Web et j'essaie d'encapsuler toutes les données sous la forme d'un objet "Result" avec des paramètres tels que Success et ErrorCodes.

Cependant, différentes méthodes produiraient des résultats et des codes d'erreur différents, mais l'objet de résultat serait généralement instancié de la même manière.

Pour gagner du temps et aussi pour en savoir plus sur les capacités async / await en C #, j'essaie d'envelopper tous les corps de méthode de mes actions d'API Web dans un délégué d'action asynchrone mais j'ai été pris dans un petit accroc ...

Compte tenu des classes suivantes:

public class Result
{
    public bool Success { get; set; }
    public List<int> ErrorCodes{ get; set; }
}

public async Task<Result> GetResultAsync()
{
    return await DoSomethingAsync<Result>(result =>
    {
        // Do something here
        result.Success = true;

        if (SomethingIsTrue)
        {
            result.ErrorCodes.Add(404);
            result.Success = false;
        }
    }
}

Je souhaite écrire une méthode qui effectue une action sur un objet Result et la renvoie. Normalement, par des méthodes synchrones, ce serait

public T DoSomethingAsync<T>(Action<T> resultBody) where T : Result, new()
{
    T result = new T();
    resultBody(result);
    return result;
}

Mais comment puis-je transformer cette méthode en une méthode asynchrone en utilisant async / await?

Voici ce que j'ai essayé:

public async Task<T> DoSomethingAsync<T>(Action<T, Task> resultBody) 
    where T: Result, new()
{
    // But I don't know what do do from here.
    // What do I await?
}
Albin Anke
la source
1
Si vous utilisez newle T, pourquoi votre méthode doit-elle être asynchrone? AFAIK dans le code utilisant des API asynchrones, il vous suffit de propager le asyncness à partir d'autres méthodes que vous utilisez.
millimoose
Désolé, je suis assez nouveau dans ce domaine encore, que voulez-vous dire quand vous dites que vous avez seulement besoin de propager, et qu'est-ce que le nouveau T a à voir avec cela?
Albin Anke
Je pense que je l'ai compris, merci millimoose vous m'avez donné quelque chose à penser.
Albin Anke
1
Pourquoi essayez-vous même de faire cette asynchrone? Le plus souvent, dans des situations de non-serveur Web, faire de faux asynchrones en enveloppant du code synchrone dans des tâches (comme vous essayez de le faire) est plus lent que de le faire de manière synchrone.
Scott Chamberlain
1
@AlbinAnke Par "propagate", je veux dire que si vous appelez une méthode .NET comme Stream.ReadAsync()dans une méthode, cette méthode devrait elle-même être asynchrone et renvoyer un Task<T>where Test ce que vous auriez retourné si la méthode était synchrone. L'idée est que de cette façon, chaque appelant de votre méthode peut alors "attendre de façon asynchrone" (je ne sais pas quel bon terme pour cela) que le sous-jacent Stream.ReadAsync()se termine. Une métaphore pour cela que vous pouvez utiliser est qu'async est "infectieux", et se propage des E / S intégrées de bas niveau vers d'autres codes dont les résultats dépendent de ceux desdites E / S.
millimoose

Réponses:

307

L' asyncéquivalent de Action<T>est Func<T, Task>, donc je pense que c'est ce que vous recherchez:

public async Task<T> DoSomethingAsync<T>(Func<T, Task> resultBody)
    where T : Result, new()
{
  T result = new T();
  await resultBody(result);
  return result;
}
Stephen Cleary
la source
@Stephen Clairement, j'essaie d'implémenter des éléments similaires dans un MVVM ligth Messenger, puis-je implémenter de la même manière?
Juan Pablo Gomez
@JuanPabloGomez: Je ne connais pas leur type de messagerie, mais je ne vois pas pourquoi cela ne fonctionnerait pas.
Stephen Cleary
1
Ceci est incroyable! Je pensais qu'il ne serait pas possible de faire une action asynchrone, et je l'ai déjà considérée comme un défaut de langage. Je n'ai pas pensé à utiliser un Func. Merci.
Noel Widmer
2
@DFSFOT: L'équivalent asynchrone d'une voidméthode est une Taskméthode -returning; ainsi, l'équivalent asynchrone de Actionest Func<Task>et l'équivalent asynchrone de Action<T>est Func<T, Task>. Plus d'infos ici .
Stephen Cleary
1
@DFSFOT: Une méthode asynchrone doit retourner Tasklorsqu'elle n'a pas de valeur de retour. S'il utilise le asyncmot - clé, l' Taskinstance réelle sera créée par une machine à états, et non directement par la fonction.
Stephen Cleary
-11

Je pense donc que la manière de mettre en œuvre ceci est:

public Task<T> DoSomethingAsync<T>(Action<T> resultBody) where T : Result, new()
{
    return Task<T>.Factory.StartNew(() =>
    {
        T result = new T();
        resultBody(result);
        return result;
    });
}
Albin Anke
la source
7
Vous devez éviter Task.Run(et encore plus StartNew) sur ASP.NET.
Stephen Cleary
Quelle est la meilleure façon de procéder?
Albin Anke
J'ai publié une réponse et j'ai également voté pour la réponse de @ svick. Ce sont toutes les deux de bonnes réponses.
Stephen Cleary