Appel asynchrone depuis du code synchrone en C# : le choc des approches

Formateur Python en ligne pour débutants

Apprenez Python facilement sans surcharge théorique. Résolvez des tâches pratiques avec vérification automatique et écrivez du code directement dans le navigateur.

Start Course

Comment appeler une méthode asynchrone depuis du code synchrone en C# ? Du concret, pas de blabla

\n\n

Imagine : tu es le soir devant ton code, tu finalises un vieil utilitaire console en C#. Tout fonctionne de manière synchrone, comme une horloge. Et là — bam ! — tu dois appeler une API qui n'existe qu'en async Task. Pas question de réécrire tout le programme ? Une douleur familière, non ?

\n\n

La question « How to call asynchronous method from synchronous method in C# » est l'une des plus populaires sur Stack Overflow. Et pour cause. Dans les commentaires, des tonnes de conseils allant de « fais .Result et ne t'inquiète pas » à « t'es fou, tu vas avoir un deadlock ! ». Décomposons tout cela : quelles approches fonctionnent vraiment, et lesquelles mènent en enfer.

\n\n

Pourquoi un simple « await » ne fonctionne-t-il pas ?

\n\n

Si tu essaies d'écrire :

\n
public void SyncMethod()\n{\n    await DoSomethingAsync(); // Erreur CS4032\n}
\n

Le compilateur refusera tout simplement de compiler. await ne peut être utilisé qu'à l'intérieur d'une méthode marquée du modificateur async. Et async void est une autre histoire que nous aborderons aussi.

\n\n

Donc, nous avons une méthode asynchrone :

\n
public async Task<string> GetDataAsync()\n{\n    using var httpClient = new HttpClient();\n    return await httpClient.GetStringAsync("https://api.example.com/data");\n}
\n\n

Et une méthode synchrone à partir de laquelle nous voulons l'appeler. Passons en revue les options.

\n\n

Approche 1 : GetAwaiter().GetResult() — le style hacker

\n\n

La méthode la plus populaire et sans doute la plus élégante :

\n
public string CallSync()\n{\n    return GetDataAsync().GetAwaiter().GetResult();\n}
\n\n

Pourquoi c'est cool :

\n
    \n
  • Ne génère pas d'exception AggregateException (contrairement à .Result). Si une erreur survient dans la méthode asynchrone, tu obtiendras son type d'origine, pas un wrapper.
  • \n
  • Le code se lit bien, sans try-catch superflu.
  • \n
\n\n

Mais il y a un hic : si ta méthode synchrone s'exécute dans un contexte avec SynchronizationContext (par exemple, dans un thread UI WinForms ou WPF), tu risques d'obtenir un deadlock — un blocage mutuel des threads. Le thread A attend l'exécution de la tâche asynchrone, et la tâche ne peut pas se terminer car elle attend que le thread A se libère. La galère.

\n\n

Comment l'éviter : Utilise .ConfigureAwait(false) dans la méthode asynchrone elle-même. Mais cela modifie l'API, pas seulement le code appelant.

\n\n

Approche 2 : Task.Run(async () => await ...).GetAwaiter() — déportation dans le pool de threads

\n\n

Si tu ne peux pas modifier la méthode asynchrone mais que tu veux éviter à coup sûr le deadlock — déporte la tâche dans le pool de threads :

\n
public string CallSyncSafe()\n{\n    return Task.Run(async () => await GetDataAsync()).GetAwaiter().GetResult();\n}
\n\n

La magie : Task.Run lance le délégué sur un thread du ThreadPool, qui n'a pas de SynchronizationContext. Le deadlock est impossible. Même si la méthode originale utilise ConfigureAwait(true).

\n\n

Inconvénient : Un changement de contexte (context switch) supplémentaire. Pour des centaines d'appels, ce n'est pas idéal. Pour des appels ponctuels, c'est parfait.

\n\n

Approche 3 : .Result ou .Wait() — la méthode de grand-mère

\n\n
public string CallSyncOldSchool()\n{\n    return GetDataAsync().Result; // Ou .Wait()\n}
\n\n

Ce qui cloche :

\n
    \n
  • En cas d'erreur, lève une AggregateException. Pour accéder à la véritable erreur, il faut fouiller dans InnerException.
  • \n
  • Toujours le même risque de deadlock dans un contexte UI.
  • \n
  • Le code ressemble à une rustine de 2012.
  • \n
\n\n

Cette approche est un chiffon rouge pour la revue de code. Si je vois ça dans une pull request, je la refuse.

\n\n

Approche 4 : AsyncHelper ou RunSync personnalisé

\n\n

Les développeurs expérimentés écrivent parfois leurs propres helpers. Par exemple, celui-ci :

\n
                

Blogs

Book Recommendations