Saltar al contenido

¿Cómo llamar al método asíncrono desde el método síncrono en C#?

Te damos la bienvenida a nuestro sitio web, en este lugar hallarás la respuesta a lo que necesitas.

Solución:

La programación asíncrona “crece” a través del código base. Se ha comparado con un virus zombie. La mejor solución es dejar 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íncrona. Sin embargo, no existe una solución que funcione en todas las situaciones.

Solución A

Si tiene un método asíncrono simple que no necesita sincronizarse con 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 sólo es adecuada si MyAsyncMethod no se sincroniza con 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 la interfaz de usuario ni acceder al contexto de solicitud de ASP.NET.

Solución B

Si MyAsyncMethod necesita sincronizarse con su contexto, entonces puede usar AsyncContext.RunTask para proporcionar un contexto anidado:

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

*Actualización 14/04/2014: En las 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 se propagará Task excepciones).

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

  1. Un método síncrono llama a un método asíncrono, obteniendo un Task.
  2. El método síncrono hace una espera de bloqueo en el Task.
  3. los async usos del método await sin ConfigureAwait.
  4. los Task no se puede completar en esta situación porque solo se completa cuando el async el método ha terminado; los async El método no puede completarse porque está intentando programar su continuación hasta el SynchronizationContexty 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 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 todos los escenarios. Por ejemplo, si el async El método espera algo que requiere un evento de interfaz de usuario para completarse, luego se interbloqueará incluso con el contexto anidado. En ese caso, podría iniciar el async método en el grupo de subprocesos:

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

Sin embargo, esta solución requiere una MyAsyncMethod eso funcionará 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 su await enunciados y use la solución A.

Actualización, 2019-05-01: Las “prácticas menos malas” actuales se encuentran en un artículo de MSDN aquí.

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

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

  • Asíncrono y en espera
  • No bloquear en código asíncrono

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¡Hago!).

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

Aquí está mi código:

public class LogReader

    ILogger _logger;

    public LogReader(ILogger logger)
    
        _logger = logger;
    

    public LogEntity GetLog()
    
        Task task = Task.Run(async () => await GetLogAsync());
        return task.Result;
    

    public async Task GetLogAsync()
    
        var result = await _logger.GetAsync();
        // more code here...
        return result as LogEntity;
    

Microsoft creó una clase AsyncHelper (interna) para ejecutar Async como Sync. La fuente se parece a:

internal static class AsyncHelper

    private static readonly TaskFactory _myTaskFactory = new 
      TaskFactory(CancellationToken.None, 
                  TaskCreationOptions.None, 
                  TaskContinuationOptions.None, 
                  TaskScheduler.Default);

    public static TResult RunSync(Func> func)
    
        return AsyncHelper._myTaskFactory
          .StartNew>(func)
          .Unwrap()
          .GetAwaiter()
          .GetResult();
    

    public static void RunSync(Func func)
    
        AsyncHelper._myTaskFactory
          .StartNew(func)
          .Unwrap()
          .GetAwaiter()
          .GetResult();
    

Las clases base de Microsoft.AspNet.Identity solo tienen métodos Async y para llamarlos como Sync hay clases con métodos de extensión que parecen (uso de ejemplo):

public static TUser FindById(this UserManager manager, TKey userId) where TUser : class, IUser where TKey : IEquatable

    if (manager == null)
    
        throw new ArgumentNullException("manager");
    
    return AsyncHelper.RunSync(() => manager.FindByIdAsync(userId));


public static bool IsInRole(this UserManager manager, TKey userId, string role) where TUser : class, IUser where TKey : IEquatable

    if (manager == null)
    
        throw new ArgumentNullException("manager");
    
    return AsyncHelper.RunSync(() => 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 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

Sección de Reseñas y Valoraciones

Nos encantaría que puedieras mostrar esta división si lograste el éxito.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *