Pregunta

tengo un public async void Foo() Método al que quiero llamar del método síncrono. Hasta ahora, todo lo que he visto en la documentación de MSDN es llamar a los métodos Async a través de métodos async, pero todo mi programa no está construido con métodos async.

¿Es esto incluso posible?

Aquí hay un ejemplo de llamar a estos métodos desde un método asincrónico: http://msdn.microsoft.com/en-us/library/hh300224(v=vs.110).aspx

Ahora estoy buscando llamar a estos métodos de asíncrono a partir de métodos de sincronización.

¿Fue útil?

Solución

La programación asincrónica "crece" a través de la base de código. Ha sido en comparación con un virus zombie. La mejor solución es permitir que crezca, pero a veces eso no es posible.

He escrito algunos tipos en mi Nito.asyncex Biblioteca para tratar con una base de código parcialmente asíncrona. Sin embargo, no hay una solución que funcione en cada situación.

Solución A

Si tiene un método asincrónico simple que no necesita sincronizarse a su contexto, entonces puede usar Task.WaitAndUnwrapException:

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

Tú haces no quiere usar Task.Wait o Task.Result Porque envuelven excepciones en AggregateException.

Esta solución solo es apropiada si MyAsyncMethod no se sincroniza a su contexto. En otras palabras, cada await en MyAsyncMethod Debería terminar con ConfigureAwait(false). Esto significa que no puede actualizar ningún elemento de interfaz de usuario o acceder al contexto de solicitud ASP.NET.

Solución B

Si MyAsyncMethod necesita sincronizarse de nuevo a su contexto, entonces puede usar AsyncContext.RunTask Para proporcionar un contexto anidado:

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

*Actualización 14/04/2014: En versiones más recientes de la biblioteca, la API es la siguiente:

var result = AsyncContext.Run(MyAsyncMethod);

(Está bien usar Task.Result en este ejemplo porque RunTask propagará Task excepciones).

La razón por la que puede necesitar AsyncContext.RunTask en vez de Task.WaitAndUnwrapException se debe a una posibilidad de punto muerto bastante sutil que ocurre en WinForms/WPF/SL/ASP.NET:

  1. Un método sincrónico llama a un método async, obteniendo un Task.
  2. El método síncrono hace una espera de bloqueo en el Task.
  3. los async Método usa await sin que ConfigureAwait.
  4. los Task no se puede completar en esta situación porque solo se completa cuando el async El método está terminado; la async El método no puede completarse porque está intentando programar su continuación al SynchronizationContext, y WinForms/WPF/SL/ASP.NET no permitirá que la continuación se ejecute porque el método síncrono ya se está ejecutando en ese contexto.

Esta es una de las razones por las que es una buena idea usar ConfigureAwait(false) dentro de cada async método tanto como sea posible.

Solución C

AsyncContext.RunTask No funcionará en cada escenario. Por ejemplo, si el async El método espera algo que requiere un evento de UI para completar, luego se bloqueará incluso con el contexto anidado. En ese caso, podría comenzar el async Método en el grupo de hilo:

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

Sin embargo, esta solución requiere un MyAsyncMethod Eso funcionará en el contexto del grupo de hilos. Por lo tanto, no puede actualizar elementos de interfaz de usuario o acceder al contexto de solicitud ASP.NET. Y en ese caso, también puede agregar ConfigureAwait(false) a su await declaraciones y usar la solución A.

Actualización, 2019-05-01: Las "prácticas menos peor" actuales están en un Artículo de MSDN aquí.

Otros consejos

Agregar una solución que finalmente resolvió mi problema, con suerte ahorra el tiempo de alguien.

En primer lugar, lea un par de artículos de Stephen Cleary:

De las "dos mejores prácticas" en "No bloquee el código async", la primera no funcionó para mí y la segunda no fue aplicable (básicamente si puedo usar await, ¡Hago!).

Así que aquí está mi solución: envuelva la llamada dentro de un Task.Run<>(async () => await FunctionAsync()); Y espero que no punto muerto más.

Aquí está mi código:

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 construyó una clase Asynchelper (interna) para ejecutar Async como sincronización. La fuente parece:

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

Las clases de base de Microsoft.aspnet.identity solo tienen métodos Async y para llamarlos como sincronización, hay clases con métodos de extensión que se parecen (uso de ejemplo):

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

Para aquellos preocupados por los términos de código de licencia, aquí hay un enlace a un código muy similar (solo agrega soporte para la cultura en el hilo) que tiene comentarios para indicar que MicSoft es con licencia de MIT. https://github.com/aspnet/aspnetidentity/blob/master/src/microsoft.aspnet.identity.core/asynchelper.cs

Async Main ahora forma parte de C# 7.2 y puede habilitarse en la configuración de compilación avanzada de proyectos.

Para C# <7.2, la forma correcta es:

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


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

Verá esto usado en mucha documentación de Microsoft, por ejemplo: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

}

Lee la palabra clave 'espera' como "Inicie esta tarea de ejecución larga, luego devuelva el control al método de llamadas". Una vez que se realiza la tarea de larga duración, ejecuta el código después. El código después del espera es similar a lo que solía ser métodos de devolución de llamada. La gran diferencia es el flujo lógico no se interrumpe, lo que hace que sea mucho más fácil de escribir y leer.

No estoy 100% seguro, pero creo que la técnica descrita en este blog debería funcionar en muchas circunstancias:

Por lo tanto, puedes usar task.GetAwaiter().GetResult() Si desea invocar directamente esta lógica de propagación.

La respuesta más aceptada no es del todo correcta. Hay una solución que funciona en cada situación: una bomba de mensajes ad-hoc (sincronización context).

El hilo de llamadas se bloqueará como se esperaba, al tiempo que se asegura de que todas las continuaciones llamadas desde la función Async no sean de punto muerto, ya que se marcarán en la sincronización ad-hoc (bomba de mensajes) que se ejecuta en el hilo de llamadas.

El código del ayudante de la bomba de mensajes 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();
            }
        }
    }
}

Uso:

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

Descripción más detallada de la bomba Async está disponible aquí.

A cualquiera que preste atención a esta pregunta ...

Si miras en Microsoft.VisualStudio.Services.WebApi Hay una clase llamada TaskExtensions. Dentro de esa clase verá el método de extensión estática Task.SyncResult(), que, como totalmente, bloquea el hilo hasta que la tarea regresa.

Internamente llama task.GetAwaiter().GetResult() lo cual es bastante simple, sin embargo, está sobrecargado para trabajar en cualquier async Método que regresa Task, Task<T> o Task<HttpResponseMessage>... azúcar sintáctica, bebé ... papá tiene un goloso.

Parece que ...GetAwaiter().GetResult() es la forma oficial de MS de ejecutar el código Async en un contexto de bloqueo. Parece funcionar muy bien para mi caso de uso.

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

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

O usar esto:

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

Puede llamar a cualquier método asincrónico desde el código síncrono, es decir, hasta que necesite await en ellos, en cuyo caso tienen que ser marcados async también.

Como muchas personas sugieren aquí, puede llamar a Wait () o dar como resultado la tarea resultante en su método sincrónico, pero luego termina con una llamada de bloqueo en ese método, que derrota el propósito de Async.

Yo realmente no puedes hacer tu método async Y no desea bloquear el método sincrónico, entonces tendrá que usar un método de devolución de llamada pasándolo como parámetro al método Continuar con la tarea.

Sé que llego tan tarde. Pero en caso de que alguien como yo quisiera resolver esto de una manera ordenada y fácil, y sin depender de otra biblioteca.

Encontré lo siguiente código 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();
}

Entonces puedes llamarlo así

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

Si quieres ejecutarlo sincron

MethodAsync().RunSynchronously()

Esos métodos de async de Windows tienen un pequeño método ingenioso llamado Astask (). Puede usar esto para que el método se devuelva como una tarea para que pueda llamar manualmente Wait () en él.

Por ejemplo, en una aplicación Silverlight de Windows Phone 8, puede hacer lo siguiente:

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

¡Espero que esto ayude!

   //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;
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top