web-dev-qa-db-fra.com

Comment appeler une méthode asynchrone à partir d'une méthode non asynchrone?

J'ai la méthode ci-dessous:

    public string RetrieveHolidayDatesFromSource() {
        var result = this.RetrieveHolidayDatesFromSourceAsync();
        /** Do stuff **/
        var returnedResult  = this.TransformResults(result.Result); /** Where result gets used **/
        return returnedResult;
    }


    private async Task<string> RetrieveHolidayDatesFromSourceAsync() {
        using (var httpClient = new HttpClient()) {
            var json = await httpClient.GetStringAsync(SourceURI);
            return json;
        }
    }

Ce qui précède ne fonctionne pas et semble ne renvoyer aucun résultat correctement. Je ne sais pas où je manque une déclaration pour forcer l'attente d'un résultat? Je veux que la méthode RetrieveHolidayDatesFromSource () renvoie une chaîne.

Ce qui suit fonctionne bien mais il est synchrone et je pense qu'il peut être amélioré? Notez que ce qui suit est synchrone dans lequel je voudrais passer à asynchrone mais je ne peux pas envelopper ma tête pour une raison quelconque.

    public string RetrieveHolidayDatesFromSource() {
        var result = this.RetrieveHolidayDatesFromSourceAsync();
        /** Do Stuff **/

        var returnedResult = this.TransformResults(result); /** This is where Result is actually used**/
        return returnedResult;
    }


    private string RetrieveHolidayDatesFromSourceAsync() {
        using (var httpClient = new HttpClient()) {
            var json = httpClient.GetStringAsync(SourceURI);
            return json.Result;
        }
    }

Suis-je en train de manquer quelque chose?

Remarque: Pour une raison quelconque, lorsque j'arrête la méthode Async ci-dessus, lorsqu'elle arrive à la ligne "var json = attendre httpClient.GetStringAsync (SourceURI)", elle sort simplement du point d'arrêt et je ne peux pas revenir dans la méthode.

17
Samuel Tambunan

Suis-je en train de manquer quelque chose?

Oui. Le code asynchrone - de par sa nature - implique que le thread actuel n'est pas utilisé pendant que l'opération est en cours. Le code synchrone - de par sa nature - implique que le thread actuel est bloqué pendant que l'opération est en cours. C'est pourquoi appeler du code asynchrone à partir d'un code synchrone n'a littéralement aucun sens. En fait, comme je le décris sur mon blog, ne approche naïve (en utilisant Result/Wait) peut facilement entraîner des blocages .

La première chose à considérer est: est-ce que mon API doit être synchrone ou asynchrone? S'il s'agit d'E/S (comme dans cet exemple), il devrait être asynchrone . Donc, ce serait une conception plus appropriée:

public async Task<string> RetrieveHolidayDatesFromSourceAsync() {
    var result = await this.DoRetrieveHolidayDatesFromSourceAsync();
    /** Do stuff **/
    var returnedResult  = this.TransformResults(result); /** Where result gets used **/
    return returnedResult;
}

Comme je le décris dans mon article sur les meilleures pratiques asynchrones , vous devriez aller "asynchroniser complètement". Si vous ne le faites pas, vous ne tirerez aucun avantage de l'async de toute façon, alors pourquoi s'embêter?

Mais disons que vous souhaitez éventuellement passer en async, mais pour le moment vous ne pouvez pas tout changer , vous voulez juste changer une partie de votre application. C'est une situation assez courante.

Dans ce cas, l'approche appropriée consiste à exposer les deux API synchrones et asynchrones. Finalement, après la mise à niveau de tous les autres codes, les API synchrones peuvent être supprimées. J'explore une variété d'options pour ce genre de scénario dans mon article sur le développement asynchrone des friches industrielles ; mon préféré est le "hack de paramètres bool", qui ressemblerait à ceci:

public string RetrieveHolidayDatesFromSource() {
  return this.DoRetrieveHolidayDatesFromSourceAsync(sync: true).GetAwaiter().GetResult();
}

public Task<string> RetrieveHolidayDatesFromSourceAsync() {
  return this.DoRetrieveHolidayDatesFromSourceAsync(sync: false);
}

private async Task<string> DoRetrieveHolidayDatesFromSourceAsync(bool sync) {
  var result = await this.GetHolidayDatesAsync(sync);
  /** Do stuff **/
  var returnedResult  = this.TransformResults(result);
  return returnedResult;
}

private async Task<string> GetHolidayDatesAsync(bool sync) {
  using (var client = new WebClient()) {
    return sync
        ? client.DownloadString(SourceURI)
        : await client.DownloadStringTaskAsync(SourceURI);
  }
}

Cette approche évite la duplication de code et évite également tout problème d'interblocage ou de réentrance commun avec d'autres solutions antipattern "sync-over-async".

Notez que je traiterais toujours le code résultant comme une "étape intermédiaire" sur le chemin vers une API correctement asynchrone. En particulier, le code interne devait se replier sur WebClient (qui prend en charge à la fois la synchronisation et l'async) au lieu du HttpClient préféré (qui ne prend en charge que l'async). Une fois que tout le code appelant est changé pour utiliser RetrieveHolidayDatesFromSourceAsync et non RetrieveHolidayDatesFromSource, je revisite cela et j'enlève toute la dette technique, en le changeant pour utiliser HttpClient et être asynchrone -seulement.

23
Stephen Cleary
public string RetrieveHolidayDatesFromSource() {
    var result = this.RetrieveHolidayDatesFromSourceAsync().Result;
    /** Do stuff **/
    var returnedResult  = this.TransformResults(result.Result); /** Where result gets used **/
    return returnedResult;
}

Si vous ajoutez . Result à l'appel asynchrone, il s'exécutera et attendra que le résultat arrive, le forçant à être synchrone

MISE À JOUR:

private static string stringTest()
{
    return getStringAsync().Result;
}

private static async Task<string> getStringAsync()
{
    return await Task.FromResult<string>("Hello");
}
static void Main(string[] args)
{
    Console.WriteLine(stringTest());

}

Pour répondre au commentaire: Cela fonctionne sans aucun problème.

5
Pedro G. Dias