programming - load data asynchronously c#




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

J'ai une méthode public async void Foo() que je veux appeler à partir de la méthode synchrone. Jusqu'à présent, tout ce que j'ai vu à partir de la documentation MSDN appelle des méthodes asynchrones via des méthodes asynchrones, mais mon programme entier 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 des méthodes de synchronisation.


Ajout d'une solution qui a finalement résolu mon problème, espérons sauver le temps de quelqu'un.

Tout d'abord lire quelques articles de Stephen Cleary :

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

Voici donc ma solution de contournement: enroulez l'appel dans un Task.Run<>(async () => await FunctionAsync()); et, espérons-le, plus d' 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;
    }
}

Ces méthodes asynchrones Windows ont une petite méthode astucieuse 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 Windows Phone 8 Silverlight, 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!


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

J'ai écrit quelques types dans ma bibliothèque Nito.AsyncEx pour traiter une base de code partiellement asynchrone. Cependant, il n'y a 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 à son contexte, vous pouvez utiliser Task.WaitAndUnwrapException :

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

Vous ne voulez pas utiliser Task.Wait ou Task.Result car ils enveloppent des exceptions dans AggregateException .

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

Solution B

Si MyAsyncMethod doit être synchronisé avec son contexte, vous pouvez 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 de la bibliothèque, l'API est la suivante:

var result = AsyncContext.Run(MyAsyncMethod);

(Il est correct d'utiliser Task.Result dans cet exemple car RunTask va propager les exceptions de Task ).

La raison pour laquelle vous pouvez avoir besoin d' AsyncContext.RunTask au lieu de Task.WaitAndUnwrapException est due à une possibilité de blocage inattendu qui se produit sur WinForms / WPF / SL / ASP.NET:

  1. Une méthode synchrone appelle une méthode async, obtenant une Task .
  2. La méthode synchrone effectue une attente de blocage sur la Task .
  3. La méthode async utilise await sans ConfigureAwait .
  4. La Task ne peut pas se terminer dans cette situation car elle se termine uniquement lorsque la méthode async est terminée; la méthode async ne peut pas terminer car elle tente de planifier sa continuation sur SynchronizationContext et WinForms / WPF / SL / ASP.NET ne permettra pas l'exécution de la continuation car 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 fonctionnera pas dans tous les scénarios. Par exemple, si la méthode async attend quelque chose qui nécessite la fin d'un événement d'interface utilisateur, vous serez bloqué même avec le contexte imbriqué. Dans ce cas, vous pouvez démarrer 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. Il ne peut donc pas mettre à jour les éléments de l'interface utilisateur ou accéder au contexte de la requête ASP.NET. Et dans ce cas, vous pouvez également ajouter ConfigureAwait(false) à ses instructions await et utiliser la solution A.


La réponse la plus acceptée n'est pas entièrement correcte. Il existe 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 toutes les suites appelées depuis la fonction async ne se bloquent pas car elles seront rassemblées sur le SynchronizationContext ad hoc (message pump) s'exécutant sur le thread appelant.

Le code de l'assistant de pompage 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 here .


Utilisez la méthode Task.Run <> pour cela.

C'est aussi comme ça que j'exécute toutes les requêtes LINQ et les opérations Entity Framework qui n'ont pas les méthodes -Async.

Si vous utilisez MVVM, regardez ces vidéos sur async et attendez.


Vous pouvez appeler n'importe quelle méthode asynchrone à partir du code synchrone, c'est-à-dire jusqu'à ce que vous ayez besoin de les await , auquel cas ils doivent également être marqués 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 bloquant dans cette méthode, ce qui détraque le but d'async.

Je ne peux vraiment pas rendre votre méthode async et vous ne voulez pas verrouiller la méthode synchrone, alors vous devrez utiliser une méthode de rappel en la passant en paramètre à la méthode ContinueWith sur la tâche.


   //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;

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é 'await' comme "démarrez cette tâche longue, puis renvoyez le contrôle à la méthode appelante". Une fois la tâche longue exécutée, elle exécute le code après elle. Le code après l'attente est similaire à ce qui était auparavant les méthodes CallBack. La grande différence étant que le flux logique n'est pas interrompu, ce qui le rend beaucoup plus facile à écrire et à lire.







async-await