web-dev-qa-db-fra.com

Appel de méthode asynchrone de manière synchrone

J'ai une méthode async:

public async Task<string> GenerateCodeAsync()
{
    string code = await GenerateCodeService.GenerateCodeAsync();
    return code;
}

J'ai besoin d'appeler cette méthode à partir d'une méthode synchrone.

Comment puis-je faire cela sans avoir à dupliquer la méthode GenerateCodeAsync pour que cela fonctionne de manière synchrone?

Mise à jour

Pourtant, aucune solution raisonnable trouvée.

Cependant, je vois que HttpClient implémente déjà ce modèle

using (HttpClient client = new HttpClient())
{
    // async
    HttpResponseMessage responseAsync = await client.GetAsync(url);

    // sync
    HttpResponseMessage responseSync = client.GetAsync(url).Result;
}
213
Catalin

Vous pouvez accéder à la propriété Result de la tâche, ce qui entraînera le blocage de votre thread jusqu'à ce que le résultat soit disponible:

string code = GenerateCodeAsync().Result;

Remarque: Dans certains cas, cela pourrait entraîner un blocage: votre appel à Result bloque le thread principal, empêchant ainsi l'exécution du reste du code asynchrone. Vous avez les options suivantes pour vous assurer que cela ne se produit pas:

263
Heinzi

Vous devez obtenir l'attendeur (GetAwaiter()) et mettre fin à l'attente de l'achèvement de la tâche asynchrone (GetResult()).

string code = GenerateCodeAsync().GetAwaiter().GetResult();
54
Diego Torres

Vous devriez pouvoir faire cela en utilisant des délégués, expression lambda

private void button2_Click(object sender, EventArgs e)
    {

        label1.Text = "waiting....";

        Task<string> sCode = Task.Run(async () =>
        {
            string msg =await GenerateCodeAsync();
            return msg;
        });

        label1.Text += sCode.Result;

    }

    private Task<string> GenerateCodeAsync()
    {
        return Task.Run<string>(() => GenerateCode());
    }

    private string GenerateCode()
    {
        Thread.Sleep(2000);
        return "I m back" ;
    }
29
Faiyaz

J'ai besoin d'appeler cette méthode à partir d'une méthode synchrone.

C'est possible avec GenerateCodeAsync().Result ou GenerateCodeAsync().Wait(), comme le suggère l'autre réponse. Cela bloquerait le thread actuel jusqu'à ce que GenerateCodeAsync soit terminé.

Cependant, votre question est taguée avec asp.net , et vous avez également laissé le commentaire:

J'espérais une solution plus simple, pensant qu'asp.net gérait cela beaucoup plus facilement que d'écrire autant de lignes de code

Mon point est, vous ne devriez pas bloquer sur une méthode asynchrone dans ASP.NET. Cela réduira l'évolutivité de votre application Web et risque de créer un blocage (lorsqu'une await continuation à l'intérieur de GenerateCodeAsync est publiée dans AspNetSynchronizationContext). Utiliser Task.Run(...).Result pour décharger quelque chose sur un thread de pool, puis le bloquer nuira davantage à l'évolutivité, car il encourt +1 thread supplémentaire pour traiter une requête HTTP donnée.

ASP.NET prend en charge les méthodes asynchrones, soit via des contrôleurs asynchrones (dans ASP.NET MVC et Web API), soit directement via AsyncManager et PageAsyncTask dans ASP.NET classique. Vous devriez l'utiliser. Pour plus de détails, consultez cette réponse .

20
noseratio

Microsoft Identity a des méthodes d'extension qui appellent des méthodes asynchrones de manière synchrone. Par exemple, il existe une méthode GenerateUserIdentityAsync () et égale à CreateIdentity ()

Si vous regardez UserManagerExtensions.CreateIdentity (), cela ressemble à ceci:

 public static ClaimsIdentity CreateIdentity<TUser, TKey>(this UserManager<TUser, TKey> manager, TUser user,
        string authenticationType)
        where TKey : IEquatable<TKey>
        where TUser : class, IUser<TKey>
    {
        if (manager == null)
        {
            throw new ArgumentNullException("manager");
        }
        return AsyncHelper.RunSync(() => manager.CreateIdentityAsync(user, authenticationType));
    }

Voyons maintenant ce que fait AsyncHelper.RunSync

  public static TResult RunSync<TResult>(Func<Task<TResult>> func)
    {
        var cultureUi = CultureInfo.CurrentUICulture;
        var culture = CultureInfo.CurrentCulture;
        return _myTaskFactory.StartNew(() =>
        {
            Thread.CurrentThread.CurrentCulture = culture;
            Thread.CurrentThread.CurrentUICulture = cultureUi;
            return func();
        }).Unwrap().GetAwaiter().GetResult();
    }

Donc, ceci est votre wrapper pour la méthode async. Et s'il vous plaît ne lisez pas les données de Result - cela bloquera potentiellement votre code dans ASP.

Il y a un autre moyen - ce qui est suspect pour moi, mais vous pouvez aussi l'envisager

  Result r = null;

            YourAsyncMethod()
                .ContinueWith(t =>
                {
                    r = t.Result;
                })
                .Wait();
17
Vitaliy Markitanov

Pour éviter les blocages, j'essaie toujours d'utiliser Task.Run() lorsque je dois appeler une méthode asynchrone de manière synchrone que @Heinzi mentionne.

Cependant, la méthode doit être modifiée si la méthode asynchrone utilise des paramètres. Par exemple, Task.Run(GenerateCodeAsync("test")).Result donne l'erreur:

Argument 1: impossible de convertir de 'System.Threading.Tasks.Task<string>' en 'System.Action'

Cela pourrait s'appeler comme ceci à la place:

string code = Task.Run(() => GenerateCodeAsync("test")).Result;
5
Ogglas

L'autre façon pourrait être si vous voulez attendre que la tâche soit terminée:

var t = GenerateCodeService.GenerateCodeAsync();
Task.WhenAll(t);
string code = t.Result;
0
frablaser