Comment appeler la méthode asynchrone de la méthode synchrone en C#?

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

est-ce 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=V110).aspx

maintenant je cherche à appeler ces méthodes async des méthodes de synchronisation.

514
demandé sur BoltClock 2012-02-18 21:49:28

11 réponses

la programmation asynchrone "croît" à travers la base de code. Il a été comparé à un virus zombie . La meilleure solution est de le laisser pousser, mais parfois ce n'est pas possible.

j'ai écrit quelques types dans mon Nito.Asyncex bibliothèque pour traiter avec une base de code partiellement asynchrone. Il n'y a pas de solution qui fonctionne dans toutes les situations, cependant.

Une Solution

si vous avez une méthode asynchrone simple qui n'a pas besoin de synchroniser retour à son contexte, alors vous pouvez utiliser Task.WaitAndUnwrapException :

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

Vous ne pas à utiliser Task.Wait ou Task.Result parce qu'elles enveloppent des exceptions dans AggregateException .

Cette solution n'est appropriée que si MyAsyncMethod ne se synchronise pas avec son contexte. En d'autres termes, tous les await dans MyAsyncMethod doit se terminer par ConfigureAwait(false) . Cela signifie qu'il ne peut pas mettre à jour les éléments de L'interface utilisateur ou accéder au ASP.NET contexte de la demande.

Solution B

si MyAsyncMethod doit se synchroniser à nouveau avec son contexte, alors vous pouvez être en mesure d'utiliser AsyncContext.RunTask pour fournir un contexte imbriqué:

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

*mise à jour 14/04/2014: dans les versions plus récentes du bibliothèque L'API est comme suit:

var result = AsyncContext.Run(MyAsyncMethod);

(il est possible d'utiliser Task.Result dans cet exemple car RunTask propagera Task exceptions).

la raison pour laquelle vous pourriez avoir besoin de AsyncContext.RunTask au lieu de Task.WaitAndUnwrapException est à cause d'une possibilité d'impasse plutôt subtile qui se produit sur WinForms/WPF/SL/ASP.NET:

  1. Une méthode synchrone appelle une méthode asynchrone, l'obtention d'un Task .
  2. la méthode synchrone fait une attente de blocage sur le Task .
  3. la "méthode 1519210920" utilise await sans ConfigureAwait .
  4. le Task ne peut pas être rempli dans cette situation parce qu'il ne l'est que lorsque la méthode async est terminée; la méthode async ne peut pas être remplie parce qu'elle tente d'inscrire sa continuation au SynchronizationContext , et WinForms/WPF/SL / ASP.NET ne permettra pas à la suite de s'exécuter parce que la méthode synchrone est déjà en cours d'exécution dans ce contexte.

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

Solution C

AsyncContext.RunTask ne marchera pas dans tous les scénarios. Par exemple, si la méthode async nécessite un événement D'interface utilisateur pour terminer, alors vous serez dans l'impasse, même avec le contexte imbriqué. Dans ce cas, vous pouvez lancer la méthode async sur le pool de threads:

var task = TaskEx.RunEx(async () => await MyAsyncMethod());
var result = task.WaitAndUnwrapException();

cependant, cette solution nécessite un MyAsyncMethod qui fonctionnera dans le contexte du pool de threads. Donc il ne peut pas mettre à jour les éléments de L'interface utilisateur ou accéder au ASP.NET contexte de la demande. Et dans ce cas, vous pouvez aussi bien ajouter ConfigureAwait(false) à ses déclarations await , et utiliser la solution A.

448
répondu Stephen Cleary 2018-02-07 13:51:55

Microsoft a construit une classe AsyncHelper (interne) pour exécuter Async comme synchronisation. 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();
    }
}

The Microsoft.AspNet.Les classes de base d'identité ont seulement des méthodes Async et pour les appeler Sync il y a 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 conditions de licence du code, Voici un lien vers le code très similaire (vient d'ajouter le soutien à la culture sur la thread) 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

145
répondu Erik Philips 2018-05-07 15:32:38

ajouter une solution qui a finalement résolu mon problème, avec un peu de chance sauve le temps de quelqu'un.

tout D'abord lu un couple d'articles de Stephen Cleary :

tiré de" two best practices "dans" Don't Block on Async Code", le premier n'a pas fonctionné pour moi et le second n'était pas applicable (en gros si je peux utiliser await , je le fais!).

donc voici mon contournement: envelopper l'appel à l'intérieur d'un Task.Run<>(async () => await FunctionAsync()); et si tout va bien pas deadlock plus.

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;
    }
}
137
répondu Tohid 2015-12-29 20:51:56

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

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

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


static async Task MainAsync()
{
   /*await stuff here*/
}
67
répondu Lee Smith 2018-02-22 13:36:02
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 "commencer cette tâche longue exécution, puis retourner le contrôle à la méthode d'appel". Une fois la tâche longue terminée, il exécute le code après lui. Le code après l'attente est similaire à ce qui était des méthodes de CallBack. La grande différence étant que le flux logique n'est pas interrompu ce qui le rend beaucoup plus facile à écrire et à lire.

42
répondu Despertar 2013-10-20 17:59:58

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

vous pouvez donc utiliser task.GetAwaiter().GetResult() si vous voulez invoquer directement cette logique de propagation.

26
répondu NStuke 2016-02-11 01:58:05

la réponse la plus acceptée n'est pas entièrement correcte. Il y a une solution qui fonctionne dans toutes les situations: une pompe à message ad hoc (SynchronizationContext).

le thread appelant sera bloqué comme prévu, tout en s'assurant que toutes les continuations appelées à partir de la fonction async ne sont pas bloquées car elles seront placées dans le contexte de synchronisation ad-hoc (pompe à message) tournant sur le thread appelant.

le code de la pompe à message ad hoc helper:

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();
            }
        }
    }
}

Utilisation:

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

une description plus détaillée de la pompe async est disponible ici .

18
répondu Robert J 2017-03-02 11:09:26

vous pouvez appeler n'importe quelle méthode asynchrone à partir du code synchrone, c'est-à-dire jusqu'à ce que vous ayez besoin de await sur eux, auquel cas ils doivent être marqués async aussi.

comme beaucoup de gens le suggèrent ici, vous pourriez appeler Wait() ou le résultat sur la tâche résultante dans votre méthode synchrone, mais alors vous finissez avec un appel de blocage dans cette méthode, qui sorte de défait le but de l'async.

je vous ne pouvez pas vraiment faire de votre méthode async et vous ne voulez pas verrouiller la méthode synchrone, alors vous allez devoir utiliser une méthode de rappel en la passant comme paramètre à la méthode ContinueWith sur la tâche.

3
répondu base2 2012-02-18 18:11:16
var result = Task.Run(async () => await configManager.GetConfigurationAsync()).ConfigureAwait(false);

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

ou utilisez ceci:

var result=result.GetAwaiter().GetResult().AccessToken
2
répondu rajesh A 2018-06-21 13:09:23
   //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;
-3
répondu Arvind Kumar Chaodhary 2014-07-29 13:17:50

ces méthodes async de windows ont une petite méthode appelée AsTask (). Vous pouvez utiliser ceci pour que la méthode retourne elle-même comme une tâche de sorte que vous puissiez appeler manuellement Wait() dessus.

par exemple, sur une application Windows Phone 8 Silverlight, vous pouvez faire ce qui suit:

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 
    // .....
}

Espérons que cette aide!

-3
répondu Foxy 2015-06-12 21:59:53