A menudo escribo código que tiene métodos de conveniencia que básicamente envuelven otros métodos. He aquí un ejemplo sencillo:

public class WithoutAsync
{
    public static ReadOnlyCollection<Response> GetResponses(IEnumerable<Request> fromRequests)
    {
        var ret = new List<Response>();

        foreach (Request r in fromRequests)
        {
            ret.Add(new Response());
        }

        return ret.AsReadOnly();
    }

    //convenience method
    public static Response GetResponse(Request fromRequest)
    {
        return GetResponses(new Request[] {fromRequest})[0];
    }
}

Ahora quiero await operaciones de larga duración, pero no puedo averiguar cómo adaptar esta metodología para usarla con TPL:

public class WithAsync
{
    public static async Task<ReadOnlyCollection<Response>> GetResponses(IEnumerable<Request> fromRequests)
    {
        var awaitableResponses = new List<Task<Response>>();

        foreach (Request r in fromRequests)
        {
            awaitableResponses.Add(Task.Run<Response>(async () =>
                {
                    await Task.Delay(10000); //simulate some long running async op.
                    return new Response();
                }));
        }

        return new List<Response>(await Task.WhenAll(awaitableResponses)).AsReadOnly();
    }

    //convenience method
    public static Task<Response> GetResponse(Request fromRequest)
    {
        return GetResponse(new Request[] { fromRequest });
    }
}

El método de conveniencia anterior obviamente no funcionará porque está tratando de devolver un Task<ReadOnlyCollection<Response>> cuando realmente necesita devolver un Task<Response>.

Esto funciona:

//convenience method
public static Task<Response> GetResponse(Request fromRequest)
{
    return new Task<Response>(new Func<Response>(() => GetResponse(new Request[] { fromRequest }).Result[0]));
}

Pero parece realmente incómodo y, lo que es más importante, bloquea en .Result[0] que está potencialmente en un hilo de la interfaz de usuario.

¿Existe alguna buena manera de lograr lo que estoy tratando de hacer?

3
rory.ap 30 ene. 2015 a las 23:31

2 respuestas

La mejor respuesta

Estás tratando de evitar hacer ese "método de conveniencia" async, pero no hay razón para hacerlo.

Lo que desea es llamar al otro método, esperar hasta que haya respuestas y luego obtener el primero y único. Puedes hacerlo haciéndolo async y usando await:

async Task<Response> GetResponseAsync(Request fromRequest)
{
    var responses = await GetResponsesAsync(new[] { fromRequest });
    return responses.Single();
}

Aunque una mejor solución en este caso específico es cambiar las cosas y hacer que el único GetResponse realmente haga el trabajo de una sola solicitud, y que los múltiples GetRsponses lo llamen en su lugar:

async Task<ReadOnlyCollection<Response>> GetResponsesAsync(IEnumerable<Request> fromRequests)
{
    return (await Task.WhenAll(fromRequests.Select(GetResponse))).ToList().AsReadOnly();
}

async Task<Response> GetResponseAsync(Request fromRequest)
{
    await Task.Delay(10000); //simulate some long running async op.
    return new Response();
}

Notas:

  • Sé que es un ejemplo, pero probablemente no haya razón para usar Task.Run en lugar de una simple llamada async.
  • La convención es nombrar los métodos async con un sufijo "Async" (es decir, GetResponseAsync).
  • También he pluralizado el nombre del método que devuelve una colección.
5
i3arnon 30 ene. 2015 a las 20:50

Todavía me quedo con la respuesta de I3arnon porque es una respuesta informativa bien escrita, pero me gustaría Envíe mi propia respuesta porque me di cuenta de que estaba casi allí. Este es el método de conveniencia async que estaba luchando por encontrar:

//convenience method
public static async Task<Response> GetResponse(Request fromRequest)
{
    return (await GetResponses(new Request[] { fromRequest }))[0];
}
0
Community 23 may. 2017 a las 12:12