Como você implementa um método delegado de ação assíncrona?

132

Um pouco de informação de fundo.

Estou aprendendo a pilha da API da Web e estou tentando encapsular todos os dados na forma de um objeto "Resultado" com parâmetros como Success e ErrorCodes.

Métodos diferentes, no entanto, produziriam resultados e códigos de erro diferentes, mas o objeto de resultado geralmente seria instanciado da mesma maneira.

Para economizar tempo e também para aprender mais sobre os recursos assíncronos / aguardados em C #, estou tentando agrupar todos os corpos de métodos das minhas ações de API da Web em um delegado de ação assíncrona, mas foi pego em alguns obstáculos ...

Dadas as seguintes classes:

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

Eu quero escrever um método que executa uma ação em um objeto Result e devolvê-lo. Normalmente através de métodos síncronos seria

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

Mas como transformar esse método em um método assíncrono usando async / waitit?

Isto é o que eu tentei:

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
fonte
1
Se você está newiniciando T, por que seu método precisa ser assíncrono? AFAIK no código usando APIs assíncronas, você só precisa propagar o asyncness de outros métodos usados.
millimoose
Desculpe, ainda sou bastante novo nisso, o que você quer dizer quando diz que só precisa se propagar e o que o novo T tem a ver com isso?
perfil completo de Albin Anke
Acho que descobri, graças milimoose você me deu algo para pensar.
Albin Anke
1
Por que você está tentando fazer isso de forma assíncrona? Mais frequentemente, em situações que não são de servidores da Web, fazer assíncrona falsa envolvendo o código síncrono em tarefas (como você está tentando fazer) é mais lento do que apenas fazê-lo de forma síncrona.
Scott Chamberlain
1
@AlbinAnke Por "propagação", quero dizer que, se você está chamando um método .NET como Stream.ReadAsync()em um método, esse método deve ser assíncrono e retornar um Task<T>onde Té o que você teria retornado se o método fosse síncrono. A idéia é que, dessa maneira, todo chamador do seu método possa "esperar de forma assíncrona" (não sei qual é um bom termo para isso) para a Stream.ReadAsync()conclusão do subjacente . Uma metáfora para isso que você pode usar é que o assíncrono é "infeccioso" e se espalha da E / S interna de baixo nível para outro código cujos resultados dependem dos da referida E / S.
millimoose

Respostas:

307

O asyncequivalente a Action<T>é Func<T, Task>, então acredito que é isso que você está procurando:

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
fonte
@ Stephen Claramente, estou tentando implementar algumas semelhantes em um MVVM ligth Messenger, posso implementar da mesma maneira?
Juan Pablo Gomez
@ JuanPabloGomez: Não conheço o tipo de mensagem deles, mas não vejo por que não funcionaria.
Stephen Cleary
1
Isso é incrível! Eu pensei que não seria possível fazer uma ação assíncrona e já a considerei uma falha de linguagem. Eu não pensei em usar um Func. Obrigado.
22618 Noel Widmer
2
@DFSFOT: O equivalente assíncrono de um voidmétodo é um Taskmétodo de retorno; assim, o equivalente assíncrono de Actioné Func<Task>e o equivalente assíncrono de Action<T>é Func<T, Task>. Mais informações aqui .
Stephen Cleary
1
@ DFSFOT: Um método assíncrono deve retornar Taskquando não tiver um valor de retorno. Se ele usar a asyncpalavra - chave, a Taskinstância real será criada por uma máquina de estado, não diretamente pela função.
Stephen Cleary
-11

Então, acredito que a maneira de implementar isso é:

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
fonte
7
Você deve evitar Task.Run(e mais ainda StartNew) no ASP.NET.
Stephen Cleary
Qual é a melhor maneira de fazer isso?
Albin Anke
Eu postei uma resposta e também votei na resposta de @ svick. Ambas são boas respostas.
perfil completo de Stephen Cleary