Richtige Implementierung von Async/Await

  • C#
  • .NET (FX) 4.5–4.8

Es gibt 26 Antworten in diesem Thema. Der letzte Beitrag () ist von nikeee13.

    ErfinderDesRades schrieb:

    Das Gui friert bekanntermassen nicht ein, wenn man einen Thread benutzt - hat mit Async ja nix zu tun.

    Ich meinte ohne Threading, also ganz normale Aufrufe. Wenn ich das jetzt richtig verstehe und man es auf asynchrone Aktionen überträgt, bringt async-await dann praktisch den Vorteil, dass man keine ResetEvents braucht?

    Grüße
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:
    Und gibts einen Unterschied zu der Methode hier (also wenn man async & await dazu schreibt)?

    C#-Quellcode

    1. public async Task<double> CalculatePi()
    2. {
    3. return await Task.Run(() => {
    4. for (int i = int.MinValue; i < int.MaxValue; i++) ;
    5. return Math.PI;
    6. });
    7. }
    Das innnere Await wäre redundant. Ob du jetzt den CalculatePi-Task awaitest oder den erstellten in der Funktion, kommt beides aufs Gleiche raus (aber solltest halt nicht beides machen).

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Rinecamo“ ()

    @Trade: ein ResetEvent habich eh noch nie gebraucht. Jetzt ganz neu benutze ich eins - im Zusammenhang mit Async - sollte demnächst als Tut freigeschaltet wern.

    @nafets: ja, eine async-Methode verhält sich doch so aussergewöhnlich - kehrt gewissermassen 2 mal zurück (genauer bricht erstmal ab, setzt dann fort und returnt richtig).
    Aber ich glaube, in diesem Fall macht das keinen Unterschied, ob nu so oder so:

    C#-Quellcode

    1. //so
    2. public Task<double> CalculatePi() {
    3. return Task.Run(() => {
    4. for (int i = int.MinValue; i < int.MaxValue; i++) ;
    5. return Math.PI;
    6. });
    7. }
    8. //oder so
    9. public async Task<double> CalculatePi2() {
    10. return await Task.Run(() => {
    11. for (int i = int.MinValue; i < int.MaxValue; i++) ;
    12. return Math.PI;
    13. });
    14. }
    Im ersten wird der laufende Task returnt, im 2. wird abgebrochen, fortgesetzt, und dann der beendete Task returnt.

    Ist dem Aufrufer piepe, denn der bricht ja ebenfalls beim await-Aufruf ab, und setzt erst fort, wenn der Task fertig ist.

    Das innere await allein kann man nicht als redundant bezeichnen, denn wenn mans wegmacht, meckert der Compiler, dass das async auch weg soll - und dann bist du genau bei meiner Lösung :P
    Ok, dann hat sich das soweit erledigt :)

    Nur noch eine kleine Frage: Gibt es einen Unterschied zwischen Task.Run und Task.Factory.StartNew?
    Auch das hat sich erledigt (hätte ich denn mal im Voraus gegoogelt...): blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx
    Task.Run scheint die richtige Methode zu sein.
    keine Ahnung. c#-ler lieben immer umständliche Formulierungen, werden also zu Task.Factory.StartNew() greifen, weil Task.Run() ist doch gar zu simpel :P

    Edit: nochmal geguckt: bietet scheints mehr Überladungen an auch für annere Tasks, auch ganz unabhängig vom Async-Kontext.

    Aber was erzähle ich dir - das nachgucken hättest du auch selbst gekonnt.

    nafets schrieb:

    Und gibts einen Unterschied zu der Methode hier (also wenn man async & await dazu schreibt)?

    In deinem Beispiel ist das Äquivalent. Es gibt aber feine Unterschiede bezüglich disposings.
    Schau dir mal die beiden Codes an:

    C#-Quellcode

    1. // 1:
    2. public static Task SendSomething(byte[] data)
    3. {
    4. using(var cl = new UdpClient())
    5. return cl.SendAsync(data, data.Length);
    6. }
    7. // 2:
    8. public static async Task SendSomething(byte[] data)
    9. {
    10. using(var cl = new UdpClient())
    11. return await cl.SendAsync(data, data.Length);
    12. }


    Beide Codes kompilieren einwandfrei, jedoch wirst du beim ersten Probleme bekommen. Bei 1. wird das Task zurückgegeben, der von SendAsync zurückgegeben wird. Direkt danach wird der UdpClient disposed. Wenn anschließend der Task awaited wird, wurde der UDP-Client schon disposed.
    Bei dem 2. hast du das nicht, da erst der Task von SendAsync awaited wird, bevor irgendwas disposed wird.

    Der Compiler macht da also in etwa sowas draus:

    C#-Quellcode

    1. // 1:
    2. public static Task SendSomething(byte[] data)
    3. {
    4. UdpClient cl;
    5. Task result;
    6. try
    7. {
    8. cl = new UdpClient();
    9. result = cl.SendAsync(data, data.Length);
    10. }
    11. finally
    12. {
    13. if(cl != null)
    14. cl.Dispose();
    15. }
    16. return result;
    17. }
    18. // 2:
    19. public static Task SendSomething(byte[] data)
    20. {
    21. UdpClient cl;
    22. Task result;
    23. try
    24. {
    25. cl = new UdpClient();
    26. result = await cl.SendAsync(data, data.Length);
    27. }
    28. finally
    29. {
    30. if(cl != null)
    31. cl.Dispose(); // dispose erst nach fertigstellung des SendAsyncs
    32. }
    33. return result;
    34. }


    Tasks sind übrigens nichts anderes, als das, was man in JavaScript als Promise kennt. Die TaskCompletionSource ist das, was man in JS einen Deferred nennt. Der Haupteinsatzzweck davon ist, aus Event-Basierten Async-Patterns einen Task zu bekommen. Async und Await kommen auch bal in JS und werden dort auch mit diesen verwendet.

    Sobald du ein await in deiner Funktion hast, wird alles, was danach kommt, in den Body eines Callbacks gepackt. Deshalb musst du bei sowas auch keinen Task returnen, sondern nur einen int:

    Quellcode

    1. public static async Task<int> Foo()
    2. {
    3. int bar = await Baz();
    4. bar *= 2;
    5. return bar;
    6. }

    Der Compiler macht dann etwas daraus, was sich in etwa so verhält:

    Quellcode

    1. public static Task<int> Foo()
    2. {
    3. return Baz().ContinueWith(t => t.Result * 2); // t.Result entspricht bar
    4. }


    Async geht bei einer Interface-Deklaration nicht, da sich das Async auf den Code im Body der Funktion bezieht, ähnlich wie unchecked und unsafe (wobei man unchecked noch nicht in einer Funktionssignatur anbringen kann).
    Von meinem iPhone gesendet