Jak zaimplementować metodę delegata akcji asynchronicznej?

134

Trochę informacji ogólnych.

Uczę się stosu Web API i próbuję hermetyzować wszystkie dane w postaci obiektu „Result” z parametrami takimi jak Success i ErrorCodes.

Jednak różne metody dałyby różne wyniki i kody błędów, ale obiekt wyniku byłby generalnie tworzony w ten sam sposób.

Aby zaoszczędzić trochę czasu, a także dowiedzieć się więcej o możliwościach async / await w C #, próbuję owinąć wszystkie treści metod moich akcji interfejsu API w asynchronicznego delegata akcji, ale wpadłem w pewien problem ...

Biorąc pod uwagę następujące klasy:

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

Chcę napisać metodę, która wykonuje akcję na obiekcie Result i zwraca ją. Zwykle byłoby to metodami synchronicznymi

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

Ale jak przekształcić tę metodę w metodę asynchroniczną przy użyciu async / await?

Oto, czego próbowałem:

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
źródło
1
Jeśli new-ing w górę T, dlaczego metoda zapotrzebowanie być asynchroniczny? AFAIK w kodzie używającym asynchronicznych interfejsów API, wystarczy propagować asyncness z innych używanych metod.
millimoose
Przepraszam, wciąż jestem w tym dość nowy, co masz na myśli, mówiąc, że musisz tylko rozmnażać, i co ma z tym wspólnego nowość T?
Albin Anke
Myślę, że to rozgryzłem, dzięki milimoose, dałeś mi coś do przemyślenia.
Albin Anke
1
Dlaczego w ogóle próbujesz to zrobić asynchronicznie? Częściej w sytuacjach poza serwerem WWW wykonanie fałszywej asynchronizacji poprzez zawijanie kodu synchronicznego do zadań (tak jak próbujesz to zrobić) jest wolniejsze niż robienie tego po prostu synchronicznie.
Scott Chamberlain,
1
@AlbinAnke Przez "propagate" mam na myśli, że jeśli wywołujesz metodę .NET, tak jak Stream.ReadAsync()w metodzie, ta metoda powinna sama być asynchroniczna i zwracać Task<T>gdzie Tjest to, co zwróciłeś, gdyby metoda była synchroniczna. Chodzi o to, że w ten sposób każdy obiekt wywołujący twoją metodę może następnie „asynchronicznie czekać” (nie wiem, jaki jest na to dobry termin) na zakończenie działania bazowego Stream.ReadAsync(). Metaforą tego, której możesz użyć, jest to, że asynchronizacja jest „zaraźliwa” i rozprzestrzenia się z wbudowanych operacji wejścia / wyjścia niskiego poziomu na inny kod, którego wyniki zależą od wyników wspomnianych operacji we / wy.
millimoose

Odpowiedzi:

312

asyncOdpowiednikiem Action<T>jest Func<T, Task>, więc wierzę, że to jest to, czego szukasz:

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
źródło
@Stephen Najwyraźniej próbuję zaimplementować podobne do komunikatora MVVM ligth, czy mogę zaimplementować w ten sam sposób?
Juan Pablo Gomez
@JuanPabloGomez: Nie znam ich rodzaju wiadomości, ale nie rozumiem, dlaczego to nie zadziała.
Stephen Cleary
1
To jest niesamowite! Pomyślałem, że nie da się wykonać akcji asynchronicznej i już uważałem to za błąd językowy. Nie myślałem o użyciu Func. Dzięki.
Noel Widmer
2
@DFSFOT: Asynchronicznym odpowiednikiem voidmetody jest Taskmetoda -returning; w ten sposób asynchroniczny odpowiednik Actionis Func<Task>i asynchroniczny odpowiednik Action<T>is Func<T, Task>. Więcej informacji tutaj .
Stephen Cleary
1
@DFSFOT: Metoda asynchroniczna powinna zostać zwrócona, Taskgdy nie ma wartości zwracanej. Jeśli używa asyncsłowa kluczowego, to właściwa Taskinstancja zostanie utworzona przez automat stanowy, a nie bezpośrednio przez funkcję.
Stephen Cleary
-12

Więc uważam, że sposobem na wdrożenie tego jest:

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
źródło
8
Powinieneś unikać Task.Run(a nawet bardziej StartNew) w ASP.NET.
Stephen Cleary
Jaki jest lepszy sposób na zrobienie tego?
Albin Anke,
Opublikowałem odpowiedź i zagłosowałem za odpowiedzią @ svick. Obie są dobrymi odpowiedziami.
Stephen Cleary