¿Cómo llamar al método asincrónico desde el método sincrónico en C #?

863

Tengo un public async void Foo()método al que quiero llamar desde un método sincrónico. Hasta ahora, todo lo que he visto en la documentación de MSDN es llamar a métodos asincrónicos a través de métodos asincrónicos, pero todo mi programa no está construido con métodos asincrónicos.

¿Es esto 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 asincrónicos desde métodos de sincronización.

Torre
fuente
2
Me encontré con esto también. Al anular un RoleProvider, no puede cambiar la firma del método del método GetRolesForUser, por lo que no puede hacer que el método sea asíncrono y, por lo tanto, no puede usar wait para llamar a la API de forma asíncrona. Mi solución temporal fue agregar métodos sincrónicos a mi clase genérica HttpClient, pero me gustaría saber si esto es posible (y cuáles podrían ser las implicaciones).
Timothy Lee Russell
1
Debido a que su async void Foo()método no devuelve un Task, significa que la persona que llama no puede saber cuándo se completa, debe devolverlo Task.
Dai
1
Vincular un q / a relacionado sobre cómo hacer esto en un subproceso de interfaz de usuario.
noseratio

Respuestas:

711

La programación asincrónica "crece" a través de la base de código. Se ha comparado 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 biblioteca Nito.AsyncEx para tratar con una base de código parcialmente asíncrono. 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 de nuevo a su contexto, puede usar Task.WaitAndUnwrapException:

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

Usted no desea utilizar Task.Waito Task.Resultporque se envuelven en excepciones AggregateException.

Esta solución solo es apropiada si MyAsyncMethodno se sincroniza de nuevo con su contexto. En otras palabras, cada awaiten MyAsyncMethoddebe terminar con ConfigureAwait(false). Esto significa que no puede actualizar ningún elemento de la interfaz de usuario ni acceder al contexto de solicitud de ASP.NET.

Solución B

Si MyAsyncMethodnecesita sincronizarse nuevamente con su contexto, entonces puede usarlo AsyncContext.RunTaskpara 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 usarlo Task.Resulten este ejemplo porque RunTaskpropagará Taskexcepciones).

La razón que puede necesitar en AsyncContext.RunTasklugar de esto Task.WaitAndUnwrapExceptionse debe a la posibilidad de un punto muerto bastante sutil que ocurre en WinForms / WPF / SL / ASP.NET:

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

Esta es una de las razones por las cuales es una buena idea usar lo más posible ConfigureAwait(false)en cada asyncmétodo.

Solución C

AsyncContext.RunTaskNo funcionará en todos los escenarios. Por ejemplo, si el asyncmétodo espera algo que requiera que se complete un evento de IU, entonces estará en punto muerto incluso con el contexto anidado. En ese caso, puede iniciar el asyncmétodo en el grupo de subprocesos:

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

Sin embargo, esta solución requiere una MyAsyncMethodque funcione en el contexto del grupo de subprocesos. Por lo tanto, no puede actualizar los elementos de la interfaz de usuario ni acceder al contexto de solicitud de ASP.NET. Y en ese caso, también puede agregar ConfigureAwait(false)a sus awaitdeclaraciones y usar la solución A.

Actualización, 01/05/2019: Las "prácticas menos peores" actuales se encuentran en un artículo de MSDN aquí .

Stephen Cleary
fuente
99
La solución A parece lo que quiero, pero parece una tarea. WaitAndUnwrapException () no llegó a .Net 4.5 RC; solo tiene task.Wait (). ¿Alguna idea de cómo hacer esto con la nueva versión? ¿O es un método de extensión personalizado que escribiste?
deadlydog el
3
WaitAndUnwrapExceptiones mi propio método de mi biblioteca AsyncEx . Las bibliotecas oficiales de .NET no proporcionan mucha ayuda para mezclar código de sincronización y asíncrono (y, en general, ¡no deberías hacerlo!). Estoy esperando .NET 4.5 RTW y una nueva computadora portátil que no sea XP antes de actualizar AsyncEx para que se ejecute en 4.5 (actualmente no puedo desarrollar para 4.5 porque estoy atascado en XP por unas pocas semanas más).
Stephen Cleary
12
AsyncContextahora tiene un Runmétodo que toma una expresión lambda, por lo que debería usarlovar result = AsyncContext.Run(() => MyAsyncMethod());
Stephen Cleary
1
Saqué tu biblioteca de Nuget, pero en realidad no parece tener un RunTaskmétodo. Lo más cercano que pude encontrar fue Run, pero eso no tiene una Resultpropiedad.
Asad Saeeduddin
3
@Asad: Sí, más de 2 años después, la API ha cambiado. Ahora puede simplemente decirvar result = AsyncContext.Run(MyAsyncMethod);
Stephen Cleary
313

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

Primero lea un par de artículos de Stephen Cleary :

De las "dos mejores prácticas" en "No bloquear en código asíncrono", la primera no funcionó para mí y la segunda no era aplicable (básicamente si puedo usar await , lo hago!).

Así que aquí está mi solución: envuelva la llamada dentro de Task.Run<>(async () => await FunctionAsync());y, con suerte, sin punto muerto .

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;
    }
}
Esconder
fuente
55
Dos años después, tengo curiosidad por saber cómo se mantiene esta solución. ¿Hay noticias? ¿Hay alguna sutileza en este enfoque que se pierde en los novatos?
Dan Esparza
26
Esto no será un punto muerto, cierto, sino simplemente porque se ve obligado a ejecutarse en un nuevo hilo, fuera del contexto de sincronización del hilo de origen. Sin embargo, hay ciertos entornos donde esto es muy desaconsejado: particularmente las aplicaciones web. Esto podría reducir a la mitad los hilos disponibles para el servidor web (un hilo para la solicitud y otro para esto). Cuanto más haces esto, peor se pone. Potencialmente, podría terminar bloqueando todo su servidor web.
Chris Pratt
30
@ChrisPratt: puede tener razón, porque Task.Run()no es una práctica recomendada en un código asíncrono. Pero, de nuevo, ¿cuál es la respuesta a la pregunta original? ¿Nunca llamar a un método asíncrono sincrónicamente? Deseamos, pero en un mundo real, a veces tenemos que hacerlo.
Tohid
1
@Tohid puedes probar la biblioteca de Stephen Cleary. He visto a personas asumir esto y el Parallel.ForEachabuso no tendrá un efecto en 'el mundo real' y, finalmente, derribó los servidores. Este código está bien para aplicaciones de consola, pero como dice @ChrisPratt, no debe usarse en aplicaciones web. Puede funcionar "ahora" pero no es escalable.
makhdumi
1
Estoy intrigado por comenzar a crear nuevas cuentas en SO respondiendo preguntas solo para obtener suficientes puntos para votar este ...
Giannis Paraskevopoulos
206

Microsoft creó una clase AsyncHelper (interna) para ejecutar Async como Sync. La fuente se ve así:

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 base Microsoft.AspNet.Identity solo tienen métodos Async y para llamarlas como Sync hay clases con métodos de extensión similares (ejemplo de uso):

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 licencia del código, 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 tiene licencia MIT de Microsoft. https://github.com/aspnet/AspNetIdentity/blob/master/src/Microsoft.AspNet.Identity.Core/AsyncHelper.cs

Erik Philips
fuente
2
Mis métodos asincrónicos esperan otros métodos asincrónicos. NO decoro ninguna de mis awaitllamadas con ConfigureAwait(false). Intenté usar AsyncHelper.RunSyncpara llamar a una función asincrónica desde la Application_Start()función en Global.asax y parece funcionar. ¿Significa esto que no AsyncHelper.RunSynces confiablemente propenso al problema de punto muerto de "volver al contexto del interlocutor" que leí en otra parte de esta publicación?
Bob.at.Indigo.Health
1
@ Bob.at.SBS depende de lo que haga el código. No es tan simple como si uso este código si estoy seguro . Esta es una forma muy mínima y semi-segura de ejecutar comandos asíncronos sincrónicamente, se puede usar fácilmente de manera inapropiada para causar puntos muertos.
Erik Philips
1
Gracias. 2 preguntas de seguimiento: 1) ¿Puede dar un ejemplo de algo que el método asincrónico quiera evitar que cause un punto muerto, y 2) los puntos muertos en este contexto a menudo dependen del tiempo? Si funciona en la práctica, ¿podría tener un punto muerto dependiente del tiempo al acecho en mi código?
Bob.at.Indigo.Health
@ Bob.at.SBS Recomendaría hacer una pregunta utilizando el botón Hacer pregunta en la esquina superior derecha. Puede incluir un enlace a esta pregunta o respuesta en su pregunta como referencia.
Erik Philips
1
@ Bob.at ... el código proporcionado por Erik funciona perfecto en Asp. net mvc5 y EF6, pero no cuando probé alguna de las otras soluciones (ConfigureAwait (false) .GetAwaiter (). GetResult () o .result) que bloquea completamente mi aplicación web
LeonardoX
151

async Main ahora forma parte de C # 7.2 y se puede habilitar en la configuración de compilación avanzada del proyecto.

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- temas-suscripciones

Lee Smith
fuente
11
No tengo idea de por qué alguien votó en contra. Esto funciono muy bien para mi. Sin esta solución, habría tenido que propagar ASYCH EN TODAS PARTES.
Prisionero CERO
11
¿Por qué es esto mejor que MainAsync().Wait()?
aplastar
8
Estoy de acuerdo. Solo necesita MainAsync (). Espere () en lugar de todo esto.
Hajjat
8
@crush Estaba describiendo cómo esto puede evitar algunos puntos muertos. En algunas situaciones, llamar a .Wait () desde un subproceso UI o asp.net provoca un punto muerto. bloqueos asíncronos
David
66
@ClintB: absolutamente no debe hacer esto en ASP.NET Core. Las aplicaciones web son particularmente vulnerables a la falta de hilo, y cada vez que haces esto, estás sacando un hilo del grupo que de otro modo se usaría para atender una solicitud. Es menos problemático para las aplicaciones de escritorio / móviles porque tradicionalmente son de un solo usuario.
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

}

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

Despertar
fuente
15
Waitenvuelve excepciones y tiene la posibilidad de un punto muerto.
Stephen Cleary
Pensé que si llamabas a un método asíncrono sin usarlo await, se ejecutaría sincrónicamente. Al menos eso funciona para mí (sin llamar myTask.Wait). En realidad, recibí una excepción cuando intenté llamar myTask.RunSynchronously()porque ya se había ejecutado.
asombro
2
Me gusta esta respuesta Buenos comentarios para editar, pequeños y elegantes. ¡Gracias por contribuir! Todavía estoy aprendiendo concurrencia, así que todo ayuda :)
kayleeFrye_onDeck
2
¿Debería esta respuesta funcionar a partir de hoy? Acabo de probarlo en un proyecto MVC Razor y la aplicación simplemente se cuelga al acceder .Result.
Se fue la codificación
8
@TrueBlueAussie Ese es el punto muerto de contexto de sincronización. Su código asincrónico vuelve al contexto de sincronización, pero la Resultllamada está bloqueando en ese momento, por lo que nunca llega allí. Y Resultnunca termina, porque está esperando a alguien que está esperando Resultque termine, básicamente: D
Luaan
40

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

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

NStuke
fuente
66
La solución A en la respuesta anterior de Stephen Cleary utiliza este método. Consulte la fuente WaitAndUnwrapException .
Orad
¿necesita usar GetResult () si la función que está llamando es nula o tarea? Quiero decir, si no quieres obtener ningún resultado
batmaci
Sí, de lo contrario no se bloqueará hasta que se complete la tarea. Alternativamente, en lugar de llamar a GetAwaiter (). GetResult () puede llamar a .Wait ()
NStuke
1
Esa es la parte de "muchas circunstancias". Depende del modelo general de subprocesos y de lo que estén haciendo otros subprocesos para determinar si existe un riesgo de punto muerto o no.
NStuke
GetAwaiter (). GetResult () aún puede causar puntos muertos. Solo desenvuelve la excepción en una más sensata.
nawfal hace
25

Sin embargo, hay una buena solución que funciona en (casi: ver comentarios) en cada situación: una bomba de mensajes ad-hoc (SynchronizationContext).

El subproceso de llamada se bloqueará como se esperaba, a la vez que se asegurará de que todas las continuas llamadas desde la función asincrónica no se bloqueen, ya que se ordenarán al SynchronizationContext ad-hoc (bomba de mensajes) que se ejecuta en el subproceso de llamada.

El código del asistente de 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(...));

Una descripción más detallada de la bomba asíncrona está disponible aquí .

Robert J
fuente
Contexto de excepción y AsyncPump stackoverflow.com/questions/23161693/…
PreguntonCojoneroCabrón
Esto no funciona en un escenario Asp.net, ya que puede perder aleatoriamente HttpContext.Current.
Josh Mouch
12

Para cualquiera que esté prestando atención a esta pregunta ...

Si miras adentro Microsoft.VisualStudio.Services.WebApihay una clase llamada TaskExtensions. Dentro de esa clase verás el método de extensión estáticaTask.SyncResult() , que simplemente bloquea el hilo hasta que la tarea regrese.

Internamente llama, lo task.GetAwaiter().GetResult()cual es bastante simple, sin embargo, está sobrecargado para funcionar en cualquier asyncmétodo que regrese Task, Task<T>oTask<HttpResponseMessage> ... azúcar sintáctica, bebé ... papá tiene un gusto por lo dulce.

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

jrypkahauer
fuente
3
Me tenías en "como bloques totalmente justos".
Dawood ibn Kareem
9
var result = Task.Run(async () => await configManager.GetConfigurationAsync()).ConfigureAwait(false);

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

O usa esto:

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

Puede llamar a cualquier método asincrónico desde un código síncrono, es decir, hasta que lo necesite await, en cuyo caso también deben marcarse async.

Como muchas personas están sugiriendo aquí, puede llamar a Wait () o Result en la tarea resultante en su método sincrónico, pero luego termina con una llamada de bloqueo en ese método, que de alguna manera anula el propósito de async.

Si realmente no puede hacer su método asyncy 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 ContinueWith en la tarea.

base2
fuente
55
Entonces eso no sería llamar al método sincrónicamente, ¿verdad?
Jeff Mercado
2
Según tengo entendido, la pregunta era ¿puedes llamar a un método asincrónico desde un método no asincrónico? Esto no implica tener que llamar al método asíncrono de manera bloqueante.
base2
Lo siento, tu "también tienen que estar marcados async" desvió mi atención de lo que realmente estabas diciendo.
Jeff Mercado
Si realmente no me importa la asincronía, ¿está bien llamarlo de esta manera (y qué pasa con la posibilidad de puntos muertos en excepciones envueltas que Stephen Cleary sigue molestando?) Tengo algunos métodos de prueba (que deben ejecutarse sincrónicamente) que prueba métodos asincrónicos. Debo esperar el resultado antes de continuar, para poder probar el resultado del método asincrónico.
asombro el
6

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

Encontré el 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>());
Wahid Bitar
fuente
66
Esto se ve exactamente como la respuesta anterior, me estoy perdiendo algo
inlokesh
2

Después de horas de probar diferentes métodos, con más o menos éxito, esto es con lo que terminé. No termina en un punto muerto mientras se obtiene el resultado y también obtiene y lanza la excepción original y no la envuelta.

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
fuente
1
Funciona con la tarea de retorno. GetAwaiter (). GetResult ();
Por G
Sí, pero ¿qué pasa con la excepción original?
Jiří Herník
.Result creo que es básicamente lo mismo que .GetAwaiter (). GetResult ()
Por G
-2

Podría llamarse desde un nuevo hilo (¡NO desde el grupo de hilos!):

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
fuente
-3

Esos métodos asincrónicos de Windows tienen un pequeño método ingenioso llamado AsTask (). Puede usar esto para que el método se devuelva como una tarea, de modo que pueda llamar manualmente a Wait () en él.

Por ejemplo, en una aplicación de Windows Phone 8 Silverlight, 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!

astuto
fuente
-4

Si quieres ejecutarlo, sincroniza

MethodAsync().RunSynchronously()
smj
fuente
3
Este método está destinado a iniciar tareas en frío. Por lo general, los métodos asíncronos devuelven una tarea activa, en otras palabras, una tarea que ya se inició. llamando RunSynchronously()a una tarea caliente resultados a un InvalidOperationException. Pruébelo con este código: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
fuente
2
Generar punto muerto. Mejor borra la respuesta.
PreguntonCojoneroCabrón
Task.Run (() => SaveAssetDataAsDraft ()). Resultado; - no genera punto muerto
Anubis