Comment appeler une méthode asynchrone à partir d'une méthode synchrone en C #?

864

J'ai une public async void Foo()méthode que je veux appeler à partir d'une méthode synchrone. Jusqu'à présent, tout ce que j'ai vu de la documentation MSDN appelle des méthodes asynchrones via des méthodes asynchrones, mais tout mon programme n'est pas construit avec des méthodes asynchrones.

Est-ce seulement possible?

Voici un exemple d'appel de ces méthodes à partir d'une méthode asynchrone: http://msdn.microsoft.com/en-us/library/hh300224(v=vs.110).aspx

Maintenant, je cherche à appeler ces méthodes asynchrones à partir de méthodes de synchronisation.

La tour
la source
2
J'ai aussi rencontré ça. En remplaçant un RoleProvider, vous ne pouvez pas modifier la signature de la méthode GetRolesForUser, vous ne pouvez donc pas rendre la méthode asynchrone et ne pouvez donc pas utiliser wait pour appeler api de manière asynchrone. Ma solution temporaire a été d'ajouter des méthodes synchrones à ma classe générique HttpClient mais j'aimerais savoir si cela est possible (et quelles pourraient être les implications).
Timothy Lee Russell
1
Parce que votre async void Foo()méthode ne retourne pas Taskcela signifie qu'un appelant ne peut pas savoir quand elle se termine, elle doit retourner à la Taskplace.
Dai
1
Lier un q / a lié sur la façon de le faire sur un thread d'interface utilisateur.
noseratio

Réponses:

712

La programmation asynchrone "grandit" à travers la base de code. Il a été comparé à un virus zombie . La meilleure solution est de lui permettre de se développer, mais parfois ce n'est pas possible.

J'ai écrit quelques types dans ma bibliothèque Nito.AsyncEx pour gérer une base de code partiellement asynchrone. Il n'y a cependant pas de solution qui fonctionne dans toutes les situations.

Solution A

Si vous avez une méthode asynchrone simple qui n'a pas besoin de se synchroniser avec son contexte, vous pouvez utiliser Task.WaitAndUnwrapException:

var task = MyAsyncMethod();
var result = task.WaitAndUnwrapException();

Vous ne pas à utiliser Task.Waitou Task.Resultparce qu'ils enveloppent exceptions AggregateException.

Cette solution n'est appropriée que si elle MyAsyncMethodne se synchronise pas avec son contexte. En d'autres termes, chaque awaitentrée MyAsyncMethoddoit se terminer par ConfigureAwait(false). Cela signifie qu'il ne peut pas mettre à jour les éléments de l'interface utilisateur ni accéder au contexte de demande ASP.NET.

Solution B

Si vous MyAsyncMethoddevez vous synchroniser avec son contexte, vous pouvez utiliser AsyncContext.RunTaskpour fournir un contexte imbriqué:

var result = AsyncContext.RunTask(MyAsyncMethod).Result;

* Mise à jour du 14/04/2014: dans les versions plus récentes de la bibliothèque, l'API est la suivante:

var result = AsyncContext.Run(MyAsyncMethod);

(Il est correct d'utiliser Task.Resultdans cet exemple car RunTaskpropage des Taskexceptions).

La raison pour laquelle vous pourriez avoir besoin à la AsyncContext.RunTaskplace Task.WaitAndUnwrapExceptionest à cause d'une possibilité de blocage plutôt subtile qui se produit sur WinForms / WPF / SL / ASP.NET:

  1. Une méthode synchrone appelle une méthode asynchrone, obtenant un Task.
  2. La méthode synchrone fait une attente de blocage sur le Task.
  3. La asyncméthode utilise awaitsans ConfigureAwait.
  4. Le Taskne peut pas terminer dans cette situation car il se termine uniquement lorsque la asyncméthode est terminée; la asyncméthode ne peut pas se terminer car elle tente de planifier sa continuation vers le SynchronizationContext, et WinForms / WPF / SL / ASP.NET n'autorisera pas la poursuite à s'exécuter car la méthode synchrone s'exécute déjà dans ce contexte.

C'est une des raisons pour lesquelles c'est une bonne idée d'utiliser autant que possible ConfigureAwait(false)dans chaque asyncméthode.

Solution C

AsyncContext.RunTaskne fonctionnera pas dans tous les scénarios. Par exemple, si la asyncméthode attend quelque chose qui nécessite un événement d'interface utilisateur, vous bloquerez même avec le contexte imbriqué. Dans ce cas, vous pouvez démarrer la asyncméthode sur le pool de threads:

var task = Task.Run(async () => await MyAsyncMethod());
var result = task.WaitAndUnwrapException();

Cependant, cette solution nécessite un MyAsyncMethodqui fonctionnera dans le contexte du pool de threads. Il ne peut donc pas mettre à jour les éléments de l'interface utilisateur ni accéder au contexte de demande ASP.NET. Et dans ce cas, vous pouvez tout aussi bien compléter ConfigureAwait(false)ses awaitdéclarations et utiliser la solution A.

Mise à jour, 2019-05-01: Les "pratiques les moins pires" actuelles sont dans un article MSDN ici .

Stephen Cleary
la source
9
La solution A semble être ce que je veux, mais elle ressemble à task.WaitAndUnwrapException () n'est pas entrée dans le .Net 4.5 RC; il n'a que task.Wait (). Une idée de comment faire cela avec la nouvelle version? Ou s'agit-il d'une méthode d'extension personnalisée que vous avez écrite?
deadlydog
3
WaitAndUnwrapExceptionest ma propre méthode de ma bibliothèque AsyncEx . Les bibliothèques officielles .NET ne fournissent pas beaucoup d'aide pour mélanger le code de synchronisation et asynchrone (et en général, vous ne devriez pas le faire!). J'attends RTW .NET 4.5 et un nouvel ordinateur portable non XP avant de mettre à jour AsyncEx pour qu'il fonctionne sur 4.5 (je ne peux pas actuellement développer pour 4.5 car je suis bloqué sur XP pendant quelques semaines de plus).
Stephen Cleary
12
AsyncContexta maintenant une Runméthode qui prend une expression lambda, vous devez donc utiliservar result = AsyncContext.Run(() => MyAsyncMethod());
Stephen Cleary
1
J'ai retiré votre bibliothèque de Nuget, mais elle ne semble pas avoir de RunTaskméthode. La chose la plus proche que j'ai pu trouver était Run, mais cela n'a pas de Resultpropriété.
Asad Saeeduddin
3
@Asad: Oui, plus de 2 ans plus tard, l'API a changé. Vous pouvez maintenant simplement direvar result = AsyncContext.Run(MyAsyncMethod);
Stephen Cleary
313

L'ajout d'une solution qui a finalement résolu mon problème permet, je l'espère, de gagner du temps à quelqu'un.

Lisez d'abord quelques articles de Stephen Cleary :

Parmi les "deux meilleures pratiques" dans "Ne pas bloquer sur le code asynchrone", la première ne fonctionnait pas pour moi et la seconde n'était pas applicable (essentiellement si je peux utiliser await, je le fais!).

Voici donc ma solution de contournement: envelopper l'appel dans un Task.Run<>(async () => await FunctionAsync());et, espérons-le, plus aucune impasse .

Voici mon code:

public class LogReader
{
    ILogger _logger;

    public LogReader(ILogger logger)
    {
        _logger = logger;
    }

    public LogEntity GetLog()
    {
        Task<LogEntity> task = Task.Run<LogEntity>(async () => await GetLogAsync());
        return task.Result;
    }

    public async Task<LogEntity> GetLogAsync()
    {
        var result = await _logger.GetAsync();
        // more code here...
        return result as LogEntity;
    }
}
Tohid
la source
5
Deux ans plus tard, je suis curieux de savoir comment cette solution résiste. Des nouvelles? Y a-t-il une subtilité dans cette approche qui se perd chez les débutants?
Dan Esparza
26
Cela ne bloquera pas, c'est vrai, mais simplement parce qu'il est forcé de s'exécuter dans un nouveau thread, en dehors du contexte de synchronisation du thread d'origine. Cependant, il existe certains environnements où cela est très mal avisé: en particulier les applications Web. Cela pourrait réduire de moitié les threads disponibles pour le serveur Web (un thread pour la demande et un pour cela). Plus vous faites cela, pire c'est. Vous pourriez potentiellement vous retrouver dans un blocage de l'ensemble de votre serveur Web.
Chris Pratt
30
@ChrisPratt - Vous avez peut-être raison, car ce Task.Run()n'est pas une bonne pratique dans un code asynchrone. Mais, encore une fois, quelle est la réponse à la question d'origine? Vous n'appelez jamais une méthode asynchrone de manière synchrone? Nous souhaitons, mais dans un monde réel, parfois nous devons.
Tohid
1
@Tohid vous pourriez essayer la bibliothèque de Stephen Cleary. J'ai vu des gens supposer cela et les Parallel.ForEachabus n'auraient pas d'effet dans «le monde réel» et finalement, ils ont arrêté les serveurs. Ce code est OK pour les applications de la console mais, comme le dit @ChrisPratt, ne doit pas être utilisé dans les applications Web. Cela peut fonctionner "maintenant" mais n'est pas évolutif.
makhdumi
1
Je suis intrigué de commencer à créer de nouveaux comptes pour répondre aux questions afin d'obtenir suffisamment de points pour voter pour celui-ci ....
Giannis Paraskevopoulos
206

Microsoft a créé une classe AsyncHelper (interne) pour exécuter Async en tant que Sync. La source ressemble à:

internal static class AsyncHelper
{
    private static readonly TaskFactory _myTaskFactory = new 
      TaskFactory(CancellationToken.None, 
                  TaskCreationOptions.None, 
                  TaskContinuationOptions.None, 
                  TaskScheduler.Default);

    public static TResult RunSync<TResult>(Func<Task<TResult>> func)
    {
        return AsyncHelper._myTaskFactory
          .StartNew<Task<TResult>>(func)
          .Unwrap<TResult>()
          .GetAwaiter()
          .GetResult();
    }

    public static void RunSync(Func<Task> func)
    {
        AsyncHelper._myTaskFactory
          .StartNew<Task>(func)
          .Unwrap()
          .GetAwaiter()
          .GetResult();
    }
}

Les classes de base Microsoft.AspNet.Identity n'ont que des méthodes Async et pour les appeler Sync, il existe des classes avec des méthodes d'extension qui ressemblent à (exemple d'utilisation):

public static TUser FindById<TUser, TKey>(this UserManager<TUser, TKey> manager, TKey userId) where TUser : class, IUser<TKey> where TKey : IEquatable<TKey>
{
    if (manager == null)
    {
        throw new ArgumentNullException("manager");
    }
    return AsyncHelper.RunSync<TUser>(() => manager.FindByIdAsync(userId));
}

public static bool IsInRole<TUser, TKey>(this UserManager<TUser, TKey> manager, TKey userId, string role) where TUser : class, IUser<TKey> where TKey : IEquatable<TKey>
{
    if (manager == null)
    {
        throw new ArgumentNullException("manager");
    }
    return AsyncHelper.RunSync<bool>(() => manager.IsInRoleAsync(userId, role));
}

Pour ceux qui sont préoccupés par les termes du code de licence, voici un lien vers un code très similaire (ajoute simplement la prise en charge de la culture sur le fil) qui a des commentaires pour indiquer qu'il est sous licence MIT par Microsoft. https://github.com/aspnet/AspNetIdentity/blob/master/src/Microsoft.AspNet.Identity.Core/AsyncHelper.cs

Erik Philips
la source
2
Mes méthodes asynchrones attendent d'autres méthodes asynchrones. Je ne décore aucun de mes awaitappels avec ConfigureAwait(false). J'ai essayé AsyncHelper.RunSyncd'appeler une fonction asynchrone à partir de la Application_Start()fonction dans Global.asax et cela semble fonctionner. Est-ce à dire que ce AsyncHelper.RunSyncn'est sûrement pas sujette au problème de blocage "retour au contexte de l'appelant" que j'ai lu ailleurs dans cet article?
Bob.at.Indigo.Health
1
@ Bob.at.SBS dépend de ce que fait votre code. Ce n'est pas aussi simple que si j'utilise ce code, suis-je en sécurité . C'est un moyen très minimal et semi-sûr d'exécuter des commandes asynchrones de manière synchrone, il peut être facilement utilisé de manière inappropriée pour provoquer des blocages.
Erik Philips
1
Merci. 2 questions de suivi: 1) Pouvez-vous donner un exemple de quelque chose que la méthode asynchrone veut éviter et qui pourrait provoquer un blocage, et 2) les blocages dans ce contexte sont-ils souvent dépendants du calendrier? Si cela fonctionne dans la pratique, est-ce que je pourrais toujours avoir un blocage dépendant du timing dans mon code?
Bob.at.Indigo.Health
@ Bob.at.SBS Je recommanderais de poser une question en utilisant le bouton Poser une question en haut à droite. Vous pouvez inclure un lien vers cette question ou une réponse dans votre question comme référence.
Erik Philips
1
@ Bob.at ... le code fourni par Erik fonctionne parfaitement sous Asp. net mvc5 et EF6, mais pas quand j'ai essayé l'une des autres solutions (ConfigureAwait (false) .GetAwaiter (). GetResult () ou .result) qui bloque complètement mon application web
LeonardoX
151

async Main fait maintenant partie de C # 7.2 et peut être activé dans les paramètres de construction avancés du projet.

Pour C # <7.2, la bonne façon est:

static void Main(string[] args)
{
   MainAsync().GetAwaiter().GetResult();
}


static async Task MainAsync()
{
   /*await stuff here*/
}

Vous verrez cela utilisé dans de nombreuses documentations Microsoft, par exemple: https://docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-dotnet-how-to-use- sujets-abonnements

Lee Smith
la source
11
Je n'ai aucune idée POURQUOI quelqu'un a voté contre. Cela a très bien fonctionné pour moi. Sans ce correctif, j'aurais dû propager ASYCH PARTOUT.
Prisonnier ZERO
11
Pourquoi est-ce mieux que ça MainAsync().Wait()?
écraser
8
Je suis d'accord. Vous avez juste besoin de MainAsync (). Wait () au lieu de tout cela.
Hajjat
8
@crush Je décrivais comment cela peut éviter certains blocages. Dans certaines situations, appeler .Wait () à partir d'un thread d'interface utilisateur ou asp.net provoque un blocage. blocages asynchrones
David
6
@ClintB: Vous ne devez absolument pas le faire dans ASP.NET Core. Les applications Web sont particulièrement vulnérables au manque de threads, et chaque fois que vous faites cela, vous extrayez un thread du pool qui serait autrement utilisé pour répondre à une demande. C'est moins problématique pour les applications de bureau / mobiles car elles sont traditionnellement mono-utilisateur.
Chris Pratt
52
public async Task<string> StartMyTask()
{
    await Foo()
    // code to execute once foo is done
}

static void Main()
{
     var myTask = StartMyTask(); // call your method which will return control once it hits await
     // now you can continue executing code here
     string result = myTask.Result; // wait for the task to complete to continue
     // use result

}

Vous lisez le mot-clé «attendre» comme «démarrer cette tâche longue, puis retourner le contrôle à la méthode appelante». Une fois la tâche de longue durée terminée, il exécute ensuite le code. Le code après l'attente est similaire à ce qui était auparavant des méthodes CallBack. La grande différence étant que le flux logique n'est pas interrompu, ce qui facilite grandement l'écriture et la lecture.

Despertar
la source
15
Waitencapsule les exceptions et a la possibilité d'un blocage.
Stephen Cleary
Je pensais que si vous appeliez une méthode asynchrone sans l'utiliser await, elle serait exécutée de manière synchrone. Au moins, cela fonctionne pour moi (sans appeler myTask.Wait). En fait, j'ai eu une exception quand j'ai essayé d'appeler myTask.RunSynchronously()car elle avait déjà été exécutée!
awe
2
J'aime cette réponse. Bons commentaires pour l'édition, petits et élégants. Merci d'avoir contribué!
J'apprends
2
Cette réponse devrait-elle encore fonctionner à partir d'aujourd'hui? Je viens de l'essayer dans un projet MVC Razor et l'application se bloque simplement lors de l'accès .Result.
Fin du codage le
8
@TrueBlueAussie C'est le blocage du contexte de synchronisation. Votre code asynchrone ramène dans le contexte de synchronisation, mais il est bloqué par l' Resultappel à ce moment-là, il n'y arrive donc jamais. Et Resultne finit jamais, car il attend quelqu'un qui attend la Resultfin, essentiellement: D
Luaan
40

Je ne suis pas sûr à 100%, mais je pense que la technique décrite dans ce blog devrait fonctionner dans de nombreuses circonstances:

Vous pouvez donc l'utiliser task.GetAwaiter().GetResult()si vous souhaitez invoquer directement cette logique de propagation.

NStuke
la source
6
La solution A dans la réponse de Stephen Cleary ci-dessus utilise cette méthode. Voir la source WaitAndUnwrapException .
2017
avez-vous besoin d'utiliser GetResult () si la fonction que vous appelez est nulle ou une tâche? Je veux dire si vous ne voulez pas obtenir de résultats
batmaci
Oui, sinon il ne se bloquera pas jusqu'à la fin de la tâche. Alternativement, au lieu d'appeler GetAwaiter (). GetResult (), vous pouvez appeler .Wait ()
NStuke
1
C'est la partie «de nombreuses circonstances». Cela dépend du modèle de thread global et de ce que font les autres threads pour déterminer s'il existe un risque de blocage ou non.
NStuke
GetAwaiter (). GetResult () peut toujours provoquer des blocages. Il ne fait que déroger l'exception à une exception plus sensible.
nawfal Il y a
25

Il existe cependant une bonne solution qui fonctionne dans (presque: voir commentaires) toutes les situations: une pompe à messages ad-hoc (SynchronizationContext).

Le thread appelant sera bloqué comme prévu, tout en garantissant que toutes les continuations appelées à partir de la fonction asynchrone ne se bloquent pas car elles seront marshalées vers le SynchronizationContext ad hoc (pompe de messages) exécuté sur le thread appelant.

Le code de l'assistant de pompe de message ad hoc:

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.Threading
{
    /// <summary>Provides a pump that supports running asynchronous methods on the current thread.</summary>
    public static class AsyncPump
    {
        /// <summary>Runs the specified asynchronous method.</summary>
        /// <param name="asyncMethod">The asynchronous method to execute.</param>
        public static void Run(Action asyncMethod)
        {
            if (asyncMethod == null) throw new ArgumentNullException("asyncMethod");

            var prevCtx = SynchronizationContext.Current;
            try
            {
                // Establish the new context
                var syncCtx = new SingleThreadSynchronizationContext(true);
                SynchronizationContext.SetSynchronizationContext(syncCtx);

                // Invoke the function
                syncCtx.OperationStarted();
                asyncMethod();
                syncCtx.OperationCompleted();

                // Pump continuations and propagate any exceptions
                syncCtx.RunOnCurrentThread();
            }
            finally { SynchronizationContext.SetSynchronizationContext(prevCtx); }
        }

        /// <summary>Runs the specified asynchronous method.</summary>
        /// <param name="asyncMethod">The asynchronous method to execute.</param>
        public static void Run(Func<Task> asyncMethod)
        {
            if (asyncMethod == null) throw new ArgumentNullException("asyncMethod");

            var prevCtx = SynchronizationContext.Current;
            try
            {
                // Establish the new context
                var syncCtx = new SingleThreadSynchronizationContext(false);
                SynchronizationContext.SetSynchronizationContext(syncCtx);

                // Invoke the function and alert the context to when it completes
                var t = asyncMethod();
                if (t == null) throw new InvalidOperationException("No task provided.");
                t.ContinueWith(delegate { syncCtx.Complete(); }, TaskScheduler.Default);

                // Pump continuations and propagate any exceptions
                syncCtx.RunOnCurrentThread();
                t.GetAwaiter().GetResult();
            }
            finally { SynchronizationContext.SetSynchronizationContext(prevCtx); }
        }

        /// <summary>Runs the specified asynchronous method.</summary>
        /// <param name="asyncMethod">The asynchronous method to execute.</param>
        public static T Run<T>(Func<Task<T>> asyncMethod)
        {
            if (asyncMethod == null) throw new ArgumentNullException("asyncMethod");

            var prevCtx = SynchronizationContext.Current;
            try
            {
                // Establish the new context
                var syncCtx = new SingleThreadSynchronizationContext(false);
                SynchronizationContext.SetSynchronizationContext(syncCtx);

                // Invoke the function and alert the context to when it completes
                var t = asyncMethod();
                if (t == null) throw new InvalidOperationException("No task provided.");
                t.ContinueWith(delegate { syncCtx.Complete(); }, TaskScheduler.Default);

                // Pump continuations and propagate any exceptions
                syncCtx.RunOnCurrentThread();
                return t.GetAwaiter().GetResult();
            }
            finally { SynchronizationContext.SetSynchronizationContext(prevCtx); }
        }

        /// <summary>Provides a SynchronizationContext that's single-threaded.</summary>
        private sealed class SingleThreadSynchronizationContext : SynchronizationContext
        {
            /// <summary>The queue of work items.</summary>
            private readonly BlockingCollection<KeyValuePair<SendOrPostCallback, object>> m_queue =
                new BlockingCollection<KeyValuePair<SendOrPostCallback, object>>();
            /// <summary>The processing thread.</summary>
            private readonly Thread m_thread = Thread.CurrentThread;
            /// <summary>The number of outstanding operations.</summary>
            private int m_operationCount = 0;
            /// <summary>Whether to track operations m_operationCount.</summary>
            private readonly bool m_trackOperations;

            /// <summary>Initializes the context.</summary>
            /// <param name="trackOperations">Whether to track operation count.</param>
            internal SingleThreadSynchronizationContext(bool trackOperations)
            {
                m_trackOperations = trackOperations;
            }

            /// <summary>Dispatches an asynchronous message to the synchronization context.</summary>
            /// <param name="d">The System.Threading.SendOrPostCallback delegate to call.</param>
            /// <param name="state">The object passed to the delegate.</param>
            public override void Post(SendOrPostCallback d, object state)
            {
                if (d == null) throw new ArgumentNullException("d");
                m_queue.Add(new KeyValuePair<SendOrPostCallback, object>(d, state));
            }

            /// <summary>Not supported.</summary>
            public override void Send(SendOrPostCallback d, object state)
            {
                throw new NotSupportedException("Synchronously sending is not supported.");
            }

            /// <summary>Runs an loop to process all queued work items.</summary>
            public void RunOnCurrentThread()
            {
                foreach (var workItem in m_queue.GetConsumingEnumerable())
                    workItem.Key(workItem.Value);
            }

            /// <summary>Notifies the context that no more work will arrive.</summary>
            public void Complete() { m_queue.CompleteAdding(); }

            /// <summary>Invoked when an async operation is started.</summary>
            public override void OperationStarted()
            {
                if (m_trackOperations)
                    Interlocked.Increment(ref m_operationCount);
            }

            /// <summary>Invoked when an async operation is completed.</summary>
            public override void OperationCompleted()
            {
                if (m_trackOperations &&
                    Interlocked.Decrement(ref m_operationCount) == 0)
                    Complete();
            }
        }
    }
}

Usage:

AsyncPump.Run(() => FooAsync(...));

Une description plus détaillée de la pompe asynchrone est disponible ici .

Robert J
la source
Contexte d'exception et AsyncPump stackoverflow.com/questions/23161693/…
PreguntonCojoneroCabrón
Cela ne fonctionne pas dans un scénario Asp.net, car vous pouvez perdre au hasard HttpContext.Current.
Josh Mouch
12

À quiconque prête attention à cette question ...

Si vous regardez, Microsoft.VisualStudio.Services.WebApiil y a une classe appelée TaskExtensions. Dans cette classe, vous verrez la méthode d'extension statique Task.SyncResult(), qui comme bloque totalement le thread jusqu'au retour de la tâche.

En interne, il appelle task.GetAwaiter().GetResult()ce qui est assez simple, mais il est surchargé pour fonctionner sur n'importe quelle asyncméthode qui retourne Task, Task<T>ou Task<HttpResponseMessage>... du sucre syntaxique, bébé ... papa a le goût sucré.

Il semble que ...GetAwaiter().GetResult()c'est la manière officielle de MS d'exécuter du code asynchrone dans un contexte de blocage. Semble fonctionner très bien pour mon cas d'utilisation.

jrypkahauer
la source
3
Tu m'as eu à "comme des blocs tout simplement".
Dawood ibn Kareem
9
var result = Task.Run(async () => await configManager.GetConfigurationAsync()).ConfigureAwait(false);

OpenIdConnectConfiguration config = result.GetAwaiter().GetResult();

Ou utilisez ceci:

var result=result.GetAwaiter().GetResult().AccessToken
rajesh A
la source
6

Vous pouvez appeler n'importe quelle méthode asynchrone à partir du code synchrone, c'est-à-dire jusqu'à ce que vous en ayez besoin await, auquel cas elles doivent également être marquées async.

Comme beaucoup de gens le suggèrent ici, vous pouvez appeler Wait () ou Result sur la tâche résultante dans votre méthode synchrone, mais vous vous retrouvez avec un appel de blocage dans cette méthode, ce qui va à l'encontre du but de l'async.

Si vous ne pouvez vraiment pas créer votre méthode asyncet que vous ne voulez pas verrouiller la méthode synchrone, vous devrez utiliser une méthode de rappel en la passant en paramètre à la méthode ContinueWith sur la tâche.

base2
la source
5
Alors, ce ne serait pas appeler la méthode de manière synchrone maintenant?
Jeff Mercado
2
Si je comprends bien, la question était de savoir si vous pouvez appeler une méthode asynchrone à partir d'une méthode non asynchrone. Cela n'implique pas d'avoir à appeler la méthode async de manière bloquante.
base2
Désolé, votre "ils doivent être marqués asyncaussi" a détourné mon attention de ce que vous disiez vraiment.
Jeff Mercado
Si je ne me soucie pas vraiment de l'asynchronisme, est-ce OK de l'appeler de cette façon (et qu'en est-il de la possibilité de blocages dans les exceptions encapsulées que Stephen Cleary continue de harceler?) J'ai quelques méthodes de test (qui doivent être exécutées de manière synchrone) qui teste les méthodes asynchrones. Je dois attendre le résultat avant de continuer, pour pouvoir tester le résultat de la méthode asynchrone.
awe
6

Je sais que je suis tellement en retard. Mais au cas où quelqu'un comme moi voudrait résoudre ce problème d'une manière ordonnée, facile et sans dépendre d'une autre bibliothèque.

J'ai trouvé le morceau de code suivant de Ryan

public static class AsyncHelpers
{
    private static readonly TaskFactory taskFactory = new
        TaskFactory(CancellationToken.None,
            TaskCreationOptions.None,
            TaskContinuationOptions.None,
            TaskScheduler.Default);

    /// <summary>
    /// Executes an async Task method which has a void return value synchronously
    /// USAGE: AsyncUtil.RunSync(() => AsyncMethod());
    /// </summary>
    /// <param name="task">Task method to execute</param>
    public static void RunSync(Func<Task> task)
        => taskFactory
            .StartNew(task)
            .Unwrap()
            .GetAwaiter()
            .GetResult();

    /// <summary>
    /// Executes an async Task<T> method which has a T return type synchronously
    /// USAGE: T result = AsyncUtil.RunSync(() => AsyncMethod<T>());
    /// </summary>
    /// <typeparam name="TResult">Return Type</typeparam>
    /// <param name="task">Task<T> method to execute</param>
    /// <returns></returns>
    public static TResult RunSync<TResult>(Func<Task<TResult>> task)
        => taskFactory
            .StartNew(task)
            .Unwrap()
            .GetAwaiter()
            .GetResult();
}

alors vous pouvez l'appeler comme ça

var t = AsyncUtil.RunSync<T>(() => AsyncMethod<T>());
Wahid Bitar
la source
6
Cela ressemble exactement à la réponse ci-dessus . Je manque quelque chose
inlokesh
2

Après des heures à essayer différentes méthodes, avec plus ou moins de succès, c'est ce que j'ai fini avec. Il ne se termine pas dans une impasse tout en obtenant le résultat et il obtient et lève également l'exception d'origine et non celle encapsulée.

private ReturnType RunSync()
{
  var task = Task.Run(async () => await myMethodAsync(agency));
  if (task.IsFaulted && task.Exception != null)
  {
    throw task.Exception;
  }

  return task.Result;
}
Jiří Herník
la source
1
Fonctionne avec la tâche de retour.GetAwaiter (). GetResult ();
Par G
oui, mais qu'en est-il de l'exception d'origine?
Jiří Herník
.Result je pense est fondamentalement le même que .GetAwaiter (). GetResult ()
Per G
-2

Il pourrait être appelé depuis un nouveau thread (PAS depuis le pool de threads!):

public static class SomeHelperClass
{ 
       public static T Result<T>(Func<T> func)
        {
            return Task.Factory.StartNew<T>(
                  () => func()
                , TaskCreationOptions.LongRunning
                ).Result;
        }
}
...
content = SomeHelperClass.Result<string>(
  () => response.Content.ReadAsStringAsync().Result
  );
Garm
la source
-3

Ces méthodes asynchrones Windows ont une astucieuse petite méthode appelée AsTask (). Vous pouvez l'utiliser pour que la méthode se retourne en tant que tâche afin que vous puissiez appeler manuellement Wait () dessus.

Par exemple, sur une application Silverlight Windows Phone 8, vous pouvez effectuer les opérations suivantes:

private void DeleteSynchronous(string path)
{
    StorageFolder localFolder = Windows.Storage.ApplicationData.Current.LocalFolder;
    Task t = localFolder.DeleteAsync(StorageDeleteOption.PermanentDelete).AsTask();
    t.Wait();
}

private void FunctionThatNeedsToBeSynchronous()
{
    // Do some work here
    // ....

    // Delete something in storage synchronously
    DeleteSynchronous("pathGoesHere");

    // Do other work here 
    // .....
}

J'espère que cela t'aides!

Rusé
la source
-4

Si vous voulez l'exécuter Sync

MethodAsync().RunSynchronously()
smj
la source
3
Cette méthode est destinée au démarrage de tâches froides. En règle générale, les méthodes asynchrones renvoient une tâche chaude, en d'autres termes une tâche qui a déjà démarré. appeler RunSynchronously()une tâche à chaud résulte en un InvalidOperationException. Essayez-le avec ce code:Task.Run(() => {}).RunSynchronously();
Theodor Zoulias
-5
   //Example from non UI thread -    
   private void SaveAssetAsDraft()
    {
        SaveAssetDataAsDraft();
    }
    private async Task<bool> SaveAssetDataAsDraft()
    {
       var id = await _assetServiceManager.SavePendingAssetAsDraft();
       return true;   
    }
   //UI Thread - 
   var result = Task.Run(() => SaveAssetDataAsDraft().Result).Result;
Arvind Kumar Chaodhary
la source
2
Générez une impasse. Mieux vaut supprimer la réponse.
PreguntonCojoneroCabrón
Task.Run (() => SaveAssetDataAsDraft ()). Result; - ne génère pas de blocage
Anubis