¿Cómo implementa un método delegado de acción asíncrona?

132

Un poco de información de fondo.

Estoy aprendiendo la pila de API web y estoy tratando de encapsular todos los datos en forma de un objeto "Resultado" con parámetros tales como Success y ErrorCodes.

Sin embargo, diferentes métodos producirían diferentes resultados y códigos de error, pero el objeto de resultado generalmente se instanciaría de la misma manera.

Para ahorrar algo de tiempo y también para aprender más sobre las capacidades asíncronas / de espera en C #, estoy tratando de envolver todos los cuerpos de método de mis acciones de API web en un delegado de acción asíncrono, pero quedé atrapado en un pequeño inconveniente ...

Dadas las siguientes clases:

public class Result
{
    public bool Success { get; set; }
    public List<int> ErrorCodes{ get; set; }
}

public async Task<Result> GetResultAsync()
{
    return await DoSomethingAsync<Result>(result =>
    {
        // Do something here
        result.Success = true;

        if (SomethingIsTrue)
        {
            result.ErrorCodes.Add(404);
            result.Success = false;
        }
    }
}

Quiero escribir un método que realice una acción en un objeto Result y devolverlo. Normalmente a través de métodos sincrónicos sería

public T DoSomethingAsync<T>(Action<T> resultBody) where T : Result, new()
{
    T result = new T();
    resultBody(result);
    return result;
}

Pero, ¿cómo transformo este método en un método asincrónico usando async / await?

Esto es lo que he intentado:

public async Task<T> DoSomethingAsync<T>(Action<T, Task> resultBody) 
    where T: Result, new()
{
    // But I don't know what do do from here.
    // What do I await?
}
Albin Anke
fuente
1
Si está newponiendo en marcha el T, ¿por qué su método debe ser asíncrono? AFAIK en el código utilizando API asíncronas, solo necesita propagar el asyncness de otros métodos que utiliza.
millimoose
Lo siento, todavía soy bastante nuevo en esto, ¿a qué te refieres cuando dices que solo necesitas propagar, y qué tiene que ver la nueva T?
Albin Anke
Creo que lo descubrí, gracias milimoose, me diste algo en qué pensar.
Albin Anke
1
¿Por qué incluso estás tratando de hacer esto asíncrono? Más a menudo, en situaciones que no son del servidor web, hacer una asincronización falsa al envolver el código sincrónico en las tareas (como está tratando de hacer) es más lento que simplemente hacerlo sincrónicamente.
Scott Chamberlain
1
@AlbinAnke Por "propagar" quiero decir que si está llamando a un método .NET como Stream.ReadAsync()en un método, ese método en sí mismo debería ser asíncrono, y devolver un Task<T>dónde Tes lo que habría devuelto si el método fuera síncrono. La idea es que de esta manera, cada persona que llama de su método puede "esperar asincrónicamente" (no sé cuál es un buen término para esto) para Stream.ReadAsync()que se complete el subyacente . Una metáfora para esto que puede usar es que el asíncrono es "infeccioso" y se propaga desde E / S incorporadas de bajo nivel a otro código cuyos resultados dependen de los de dicha E / S.
millimoose

Respuestas:

307

El asyncequivalente de Action<T>es Func<T, Task>, así que creo que esto es lo que estás buscando:

public async Task<T> DoSomethingAsync<T>(Func<T, Task> resultBody)
    where T : Result, new()
{
  T result = new T();
  await resultBody(result);
  return result;
}
Stephen Cleary
fuente
@Stephen Claramente estoy tratando de implementar algo similar en un MVVM ligth Messenger, ¿puedo implementarlo de la misma manera?
Juan Pablo Gómez
@JuanPabloGomez: No estoy familiarizado con su tipo de mensajes, pero no veo por qué no funcionaría.
Stephen Cleary
1
¡Esto es increíble! Pensé que no sería posible realizar una Acción asíncrona, y ya lo consideraba un defecto del lenguaje. No pensé en usar un Func. Gracias.
Noel Widmer
2
@DFSFOT: el equivalente asíncrono de un voidmétodo es un Taskmétodo de retorno; por lo tanto, el equivalente asíncrono de Actionis Func<Task>y el equivalente asíncrono de Action<T>is Func<T, Task>. Más información aquí .
Stephen Cleary
1
@DFSFOT: un método asíncrono debería regresar Taskcuando no tiene un valor de retorno. Si usa la asyncpalabra clave, la Taskinstancia real será creada por una máquina de estado, no por la función directamente.
Stephen Cleary
-11

Así que creo que la forma de implementar esto es:

public Task<T> DoSomethingAsync<T>(Action<T> resultBody) where T : Result, new()
{
    return Task<T>.Factory.StartNew(() =>
    {
        T result = new T();
        resultBody(result);
        return result;
    });
}
Albin Anke
fuente
77
Debe evitar Task.Run(y aún más StartNew) en ASP.NET.
Stephen Cleary
¿Cuál es una mejor manera de hacer esto?
Albin Anke
Publiqué una respuesta, y también voté por la respuesta de @ svick. Ambas son buenas respuestas.
Stephen Cleary