Question

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

Est-ce même possible?

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

Maintenant, je suis à la recherche en appelant ces méthodes asynchrones de méthodes de synchronisation.

Était-ce utile?

La solution

Programmation asynchrone ne « croître » à travers la base de code. Il a été href="https://blogs.msdn.microsoft.com/lucian/2011/04/15/async-ctp-refresh-design-changes/" par rapport à un virus zombie . La meilleure solution est de lui permettre de grandir, mais parfois ce n'est pas possible.

Je l'ai écrit quelques types dans ma bibliothèque Nito.AsyncEx pour traiter un code partiellement asynchrone base. Il n'y a pas solution qui fonctionne dans toutes les situations, cependant.

Solution A

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

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

Vous faites pas veulent utiliser Task.Wait ou Task.Result parce qu'ils enveloppent exceptions dans AggregateException.

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

Solution B

Si MyAsyncMethod n'a pas besoin de synchroniser à 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 les plus récentes de la bibliothèque de l'API est comme suit:

var result = AsyncContext.Run(MyAsyncMethod);

(Il va bien à l'utilisation Task.Result dans cet exemple parce que RunTask propagera exceptions Task).

La raison pour laquelle vous devrez peut-être AsyncContext.RunTask au lieu de Task.WaitAndUnwrapException est à cause d'une possibilité de blocage plutôt subtile qui se passe sur WinForms / WPF / SL / ASP.NET:

  1. Procédé synchrone appelle une méthode asynchrone, l'obtention d'un Task.
  2. La méthode synchrone fait un blocage d'attente sur le Task.
  3. La méthode de async utilisations await sans ConfigureAwait.
  4. Le Task ne peut pas terminer dans cette situation parce qu'elle complète que lorsque la méthode de async est fini; la méthode async ne peut pas complète parce qu'il tente de planifier sa continuation au SynchronizationContext et WinForms / WPF / SL / ASP.NET ne permettra pas la poursuite de fonctionner parce que la méthode synchrone est en cours d'exécution dans ce contexte.

Ceci est une raison pour laquelle il est une bonne idée d'utiliser ConfigureAwait(false) au sein de chaque méthode async autant que possible.

Solution C

AsyncContext.RunTask ne fonctionne pas dans tous les scénarios. Par exemple, si la méthode async attend quelque chose qui a besoin d'un événement d'interface utilisateur pour complète, alors vous IMPASSE même avec le contexte imbriqué. Dans ce cas, vous pouvez commencer la méthode async sur le pool de threads:

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

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

Mise à jour, 01/05/2019: Le courant "moins pires pratiques" sont dans une article MSDN .

Autres conseils

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

lire tout d'abord quelques articles de Stephen Cleary:

A partir des « deux meilleures pratiques » dans « Ne pas obstruer le code Async », le premier n'a pas fonctionné pour moi et le second était pas applicable (essentiellement si je peux utiliser await, je fais!) .

Alors, voici ma solution de contournement. Envelopper l'appel dans un Task.Run<>(async () => await FunctionAsync()); et je l'espère pas impasse 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;
    }
}

Microsoft a construit une AsyncHelper classe (interne) pour exécuter Async comme Sync. Les regards source comme:

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 de Microsoft.AspNet.Identity n'ont des méthodes Async et afin de les appeler comme 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 les personnes concernées sur les conditions de licence de code, voici un lien vers un code très similaire (ajoute tout soutien à la culture sur le fil) qui a des commentaires pour indiquer qu'il est MIT sous licence par Microsoft. https://github.com/aspnet /AspNetIdentity/blob/master/src/Microsoft.AspNet.Identity.Core/AsyncHelper.cs

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

Pour C # <7.2, la manière correcte est:

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


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

Vous verrez cela utilisé dans beaucoup de documentation Microsoft, par exemple: https : //docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-dotnet-how-to-use-topics-subscriptions

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 avez lu le « await » mot-clé comme « commencer cette tâche longue course, puis revenir le contrôle à la méthode d'appel ». Une fois la tâche longue est fait, il exécute le code après. Le code après l'await est similaire à ce qui était autrefois les méthodes callback. La grande différence étant le flux logique ne soit pas interrompu ce qui le rend beaucoup plus facile à écrire et à lire.

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 ainsi utiliser task.GetAwaiter().GetResult() si vous voulez appeler directement cette logique de propagation.

La réponse la plus acceptée est pas tout à fait correct. Il y a une solution qui fonctionne dans toutes les situations. Une pompe de message ad hoc (SynchronizationContext)

Le thread appelant sera bloqué comme prévu, tout en veillant à ce que tous les continuations appelés de la fonction async n'IMPASSE pas comme ils vont être mises à profit pour la (pompe de messages) ad-hoc SynchronizationContext en cours d'exécution sur le thread appelant.

Le code de l'aide de la 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();
            }
        }
    }
}

Utilisation:

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

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

Pour toute personne de prêter attention à cette question plus ...

Si vous regardez dans Microsoft.VisualStudio.Services.WebApi il y a une classe appelée TaskExtensions. Au sein de cette classe, vous verrez la méthode d'extension statique Task.SyncResult(), qui, comme TOTALLEMENT bloque le fil jusqu'à ce que le retour de la tâche.

En interne, il appelle task.GetAwaiter().GetResult() ce qui est assez simple, mais il est surchargé de travail sur une méthode de async que Task de retour, Task<T> ou Task<HttpResponseMessage> ... sucre syntaxique, bébé ... de papa obtenu une dent sucrée.

On dirait que ...GetAwaiter().GetResult() est la façon dont les MS-officiels pour exécuter du code async dans un contexte de blocage. Il semble fonctionner très bien pour mon cas d'utilisation.

var result = Task.Run(async () => await configManager.GetConfigurationAsync()).ConfigureAwait(false);

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

Ou utilisez ceci:

var result=result.GetAwaiter().GetResult().AccessToken

Vous pouvez appeler une méthode asynchrone à partir du code synchrone, qui est, jusqu'à ce que vous devez await sur eux, auquel cas ils doivent être marqués async aussi.

Comme beaucoup de gens suggère ici, vous pouvez appeler Wait () ou le résultat sur la tâche résultant dans votre méthode synchrone, mais vous vous retrouvez avec un appel de blocage dans cette méthode, qui sorte de défaites dans le but de async .

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

Je sais que je suis si tard. Mais au cas où quelqu'un comme moi voulait résoudre ce dans un cadre soigné, facile et sans dépendre d'une autre bibliothèque.

J'ai trouvé le morceau de code 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();
}

vous pouvez l'appeler comme ça

var t = AsyncUtil.RunSync<T>(() => AsyncMethod<T>());

Si vous voulez exécuter Sync

MethodAsync().RunSynchronously()

Les fenêtres ont un async méthodes peu nifty méthode appelée AsTask (). Vous pouvez l'utiliser pour avoir le retour de la méthode elle-même comme une tâche de sorte que vous pouvez appeler manuellement Wait () sur elle.

Par exemple, sur une application Silverlight Windows Phone 8, 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 
    // .....
}

Hope this helps!

   //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;
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top