Async Await - Form friert

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

Es gibt 10 Antworten in diesem Thema. Der letzte Beitrag () ist von fichz.

    Async Await - Form friert

    yo Leute,

    ich hatte eben ein interessantes Problem mit Async und Await. Kurz zum Programm: Es wird ein Ordner ausgewählt wo Logdateien gespeichert sind. Alles zusammen hat dieser Ordner rund 250 Dateien welche insgesamt ~ 500MB an Daten beinhalten.
    Diese muss ich alle Zeile für Zeile einlesen um etwas zu überprüfen.

    Der Code sieht abgespeckt folgendermaßen aus:
    Spoiler anzeigen

    C#-Quellcode

    1. public List<StoreInfo> StoreInfos { get; set; }
    2. public DirectoryInfo Directory { get; set; }
    3. public string SearchPattern { get; set; }
    4. public Progress<string> Progress { get; set; }
    5. public decimal HourStart { get; set; }
    6. public decimal HourEnd { get; set; }
    7. public async Task ReadFiles()
    8. {
    9. this.StoreInfos = new List<StoreInfo>();
    10. await Task.Run( async () =>
    11. {
    12. foreach(var file in Directory.GetFiles(SearchPattern))
    13. {
    14. var storeInfo = new StoreInfo();
    15. storeInfo.StoreId = int.Parse(Regex.Match(file.Name, @"\d+").Value);
    16. ((IProgress<string>)Progress).Report(string.Format("Datei '{0}' wird gelesen", file.Name));
    17. var errors = await ReadAllLinesAsync(file.FullName, Encoding.Default);
    18. storeInfo.ProgramOnlyStartCount = errors;
    19. var existingEntry = StoreInfos.FirstOrDefault((s) => s.StoreId == storeInfo.StoreId);
    20. if(existingEntry == null)
    21. StoreInfos.Add(storeInfo);
    22. else
    23. existingEntry.ProgramOnlyStartCount += storeInfo.ProgramOnlyStartCount;
    24. }
    25. });
    26. ((IProgress<string>)Progress).Report("Alle Dateien wurden gelesen!");
    27. }
    28. private async Task<int> ReadAllLinesAsync(string filepath, Encoding encoding)
    29. {
    30. var lastLine = string.Empty;
    31. var startCount = 0;
    32. using(var reader = new StreamReader(filepath, encoding))
    33. {
    34. string line;
    35. while((line = await reader.ReadLineAsync()) != null)
    36. {
    37. int hour = 0;
    38. if(line.Contains(_startText) && int.TryParse(line.Substring(9, 2), out hour) && hour >= HourStart && hour <= HourEnd)
    39. {
    40. if(lastLine != string.Empty && !lastLine.Contains(_endText))
    41. startCount++;
    42. }
    43. lastLine = line;
    44. }
    45. }
    46. return startCount;
    47. }​


    Die Mainform abonniert das ProgressChanged Event:

    C#-Quellcode

    1. ​public MainForm()
    2. {
    3. InitializeComponent();
    4. InfoLabel.Text = string.Empty;
    5. _storeInfoManager = new StoreInfoManager();
    6. _storeInfoManager.Progress.ProgressChanged += Progress;
    7. }
    8. private void Progress(object sender, string message)
    9. {
    10. InfoLabel.Text = message;
    11. }


    So wie der Code jetzt dasteht wird alles korrekt gelesen, die Form ist NICHT blockiert und des InfoLabel gibt den geänderten Text korrekt aus:

    Wenn ich aber aus der Methode ReadFiles() das await Task.Run( async () => entferne, werden die paar ersten Files im InfoLabel noch angezeigt und dann hängt die Form komplett. Das heißt, dass ich nichts mehr auf der Form klicken kann. Nicht einmal das X. Es steht auch nicht "Keine Rückmeldung", wie man es überlichweise kennt in der Titelleiste. Man kann einfach nicht interagieren mit der Form bis der Leseprozess beendet ist.

    Mich würde eben interessieren warum ich dieses zusätzliche Task.Run hier noch benötige, da die Methode ja ansich schon als async deklariert wurde.
    Vielleicht hat wer eine Idee dazu.

    lg
    ScheduleLib 0.0.1.0
    Kleine Lib zum Anlaufen von Code zu bestimmten Zeiten
    @fichz Kannst Du mal ein reproduzierendes Projekt anhängen, und ggf. eine auszuweertende Datei, die dann zu vervielfältigen wäre?
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Hab mal eine Solution angehängt.
    Für die zu lesenden Files einfach ein paar .log Dateien in einem Verzeichnis erstellen. Meine LogFiles haben im Schnitt 1-2 MB. Anzahl ca. 250.
    Es reicht wenn in den Logfiles einfach lauter "X" drin stehen.

    Das Task.Run ist derzeit auskommentiert. Kommentiert man es ein - so funktioniert es wie gewünscht.

    lg
    Dateien
    ScheduleLib 0.0.1.0
    Kleine Lib zum Anlaufen von Code zu bestimmten Zeiten
    @fichz Merkwürden.
    Ich hab 12 MB in 6000 Dateien, während er liest, kann ich die Form frei bewegen.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Bei mir lief das auch ohne Probleme, allerdings beim verschieben der Form, ruckelte sie ab und zu minimal.

    Edit: @fichz
    Allerdings lief es besser wenn ich Task.ConfigureAwait(true) verwendete.

    C#-Quellcode

    1. await _storeInfoManager.ReadFiles().ConfigureAwait(true);


    And i think to myself... what a wonderfuL World!

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „Eddy“ ()

    Könntet ihr es bitte auch probieren mit Dateien die ~ 20000 Zeilen und dann ca. 1 MB haben? Das sollten so ca 70 Zeichen pro Zeile sein.
    @Eddy ich hatte es vorher bereits mit ​.ConfigureAwait(false) versucht. Hatte aber auch keine Besserung bei mir bewirkt.

    lg
    ScheduleLib 0.0.1.0
    Kleine Lib zum Anlaufen von Code zu bestimmten Zeiten
    Ok, hatte erst kleinere Dateien genutzt, laengere aber deutlich weniger Zeilen. Mit deiner Groessenvorgabe, ist beim verschieben aus dem Ruckeln ein richtiges "Springen" der Form geworden.

    Ich habe nun aber etwas gefunden, zu Task.Run

    You can use Task.Run to move CPU-bound work to a background thread, but a background thread doesn't help with a process that's just waiting for results to become available.

    msdn.microsoft.com/library/hh191443(vs.110).aspx

    Siehe dort im Bereich Threads.
    And i think to myself... what a wonderfuL World!
    Wenn ich den Part mit Task.Run() einkommentiere funktioniert es ohne Probleme. Auch ohne Ruckeln der Form.
    Soweit ich weiß ist der Sinn von async/await ist ja unter anderem, dass die Form eben nicht blockiert wird.

    Wollte nur wissen, warum dies denn noch zusätzlich von Nöten ist.
    Vielleicht liegt es aber auch an meinem PC. (Win7 32 bit)

    lg
    ScheduleLib 0.0.1.0
    Kleine Lib zum Anlaufen von Code zu bestimmten Zeiten
    Vielleicht vom Verständnis her nochmal zu Erklärung was es mit Async/Await auf sich hat. Eine mit „async“ bezeichnete „void“ Methode läuft nicht automatisch Asynchron. Es ist nur ein Kennzeichen dafür, dass die Methode vorzeitig zurückkommen kann, um dann zu einem Späteren Zeitpunkt wieder weiter zu machen. Vergleichbar mit „yield“. Das bedeute auch, dass alles in dieser Methode von demselben Thread ausgeführt wird, der diese auch aufgerufen hat. Eine "async" Methode ohne "await" ist daher nur eine ganz normale Methode. Das „await“ auf einen Task sorgt jetzt dafür, dass diese Unterbrechung stattfindet. Die Methode ist damit erstmal zu Ende und der Thread (z.B. UI-Thread) läuft weiter. Wenn dann der Task fertig ist springt der Thread wieder in die Methode zu der Stelle mit dem „await“, nimmt das Ergebnis vom Task entgegen und macht dann von da aus weiter.

    Du musst also alles was Zeit braucht schon in einen Task auslagern und mit await darauf warten. Im Gegensatz zu einem komplett eigenständigen Task/Thread sparst du dir also das invoken auf den UI-Thread, da du innerhalt der async Methode ja immer wieder im UI-Thread bist.