2012-10-31 4 views
43

Ich habe den folgenden Code,Wie Async Files.ReadAllLines und warten auf Ergebnisse?

private void button1_Click(object sender, RoutedEventArgs e) 
    { 
     button1.IsEnabled = false; 

     var s = File.ReadAllLines("Words.txt").ToList(); // my WPF app hangs here 
     // do something with s 

     button1.IsEnabled = true; 
    } 

Words.txt eine Tonne von Worten hat, die ich in das s-Variable gelesen, ich versuche Verwendung von async und await Schlüsselwort in C# 5 mit Async CTP Library so die WPF-Anwendung zu machen doesn nicht hängen. Bisher habe ich den folgenden Code haben,

private async void button1_Click(object sender, RoutedEventArgs e) 
    { 
     button1.IsEnabled = false; 

     Task<string[]> ws = Task.Factory.FromAsync<string[]>(
      // What do i have here? there are so many overloads 
      ); // is this the right way to do? 

     var s = await File.ReadAllLines("Words.txt").ToList(); // what more do i do here apart from having the await keyword? 
     // do something with s 

     button1.IsEnabled = true; 
    } 

Das Ziel ist es, die Datei in async anstatt sync zu lesen, das Einfrieren von WPF-Anwendung zu vermeiden.

Jede Hilfe wird geschätzt, Danke!

+1

Wie wäre es mit Start durch Entfernen des unnötigen Aufrufs ToList(), die eine Kopie des String-Array machen wird? –

+2

@JbEvain - Um pedantisch zu sein, 'ToList()' kopiert nicht nur das Array, es erstellt eine 'List'. Ohne weitere Informationen kann man nicht davon ausgehen, dass es unnötig ist, da "' '' '' '' '' '' '' s etwas mit '''' 'List''-Methoden nennt. – Mike

Antwort

80

UPDATE: Async Versionen von File.ReadAll[Lines|Bytes|Text], File.AppendAll[Lines|Text] und File.WriteAll[Lines|Bytes|Text] haben jetzt merged into .NET Core gewesen. Diese Methoden werden hoffentlich zurück nach .NET Framework, Mono usw. portiert und in eine zukünftige Version von .NET Standard gerollt.

Verwenden Task.Run, die im Wesentlichen eine Wrapper für Task.Factory.StartNew ist, für asynchrone Wrapper is a code smell.

Wenn Sie nicht, durch die Verwendung einer Sperrfunktion eines CPU Thread verschwenden wollen, sollten Sie eine wirklich asynchrone IO-Methode, StreamReader.ReadToEndAsync, wie dies erwarten:

using (var reader = File.OpenText("Words.txt")) 
{ 
    var fileText = await reader.ReadToEndAsync(); 
    // Do something with fileText... 
} 

Dies wird die gesamte Datei als eine bekommen string anstelle von List<string>. Wenn Sie Linien stattdessen benötigen, könnte man leicht die Saite danach aufgeteilt, wie folgt aus:

using (var reader = File.OpenText("Words.txt")) 
{ 
    var fileText = await reader.ReadToEndAsync(); 
    return fileText.Split(new[] { Environment.NewLine }, StringSplitOptions.None); 
} 

EDIT: Hier sind einige Methoden, um den gleichen Code wie File.ReadAllLines, aber in einer wirklich asynchron zu erreichen. Der Code wird über die Umsetzung der File.ReadAllLines selbst basiert:

using System.Collections.Generic; 
using System.IO; 
using System.Text; 
using System.Threading.Tasks; 

public static class FileEx 
{ 
    /// <summary> 
    /// This is the same default buffer size as 
    /// <see cref="StreamReader"/> and <see cref="FileStream"/>. 
    /// </summary> 
    private const int DefaultBufferSize = 4096; 

    /// <summary> 
    /// Indicates that 
    /// 1. The file is to be used for asynchronous reading. 
    /// 2. The file is to be accessed sequentially from beginning to end. 
    /// </summary> 
    private const FileOptions DefaultOptions = FileOptions.Asynchronous | FileOptions.SequentialScan; 

    public static Task<string[]> ReadAllLinesAsync(string path) 
    { 
     return ReadAllLinesAsync(path, Encoding.UTF8); 
    } 

    public static async Task<string[]> ReadAllLinesAsync(string path, Encoding encoding) 
    { 
     var lines = new List<string>(); 

     // Open the FileStream with the same FileMode, FileAccess 
     // and FileShare as a call to File.OpenText would've done. 
     using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, DefaultBufferSize, DefaultOptions)) 
     using (var reader = new StreamReader(stream, encoding)) 
     { 
      string line; 
      while ((line = await reader.ReadLineAsync()) != null) 
      { 
       lines.Add(line); 
      } 
     } 

     return lines.ToArray(); 
    } 
} 
+0

Ich wusste nicht, danke thank khellang :) –

+6

Dies wichtig Windows-E/A-Ports, um dies ohne keine CPU-Threads erwartet, während die Task.Factory.StartNew/Task.Run-Ansatz in einer anderen Antwort verschwendet einen CPU-Thread. Der Ansatz dieser Antwort ist effizienter. –

+1

FYI; Ich habe Async-Versionen dieser APIs unter https://github.com/dotnet/corefx/issues/11220 vorgeschlagen. Mal sehen, wie es geht :) – khellang

-3

Try this:

private async void button1_Click(object sender, RoutedEventArgs e) 
{ 
    button1.IsEnabled = false; 
    try 
    { 
     var s = await Task.Run(() => File.ReadAllLines("Words.txt").ToList()); 
     // do something with s 
    } 
    finally 
    { 
     button1.IsEnabled = true; 
    } 
} 

Edit:

Sie die Anprobe schließlich nicht brauchen, damit dies funktioniert. Es ist wirklich nur die eine Zeile, die Sie ändern müssen. Um zu erklären, wie es funktioniert: Das erzeugt einen anderen Thread (erhält tatsächlich einen aus dem Thread-Pool) und ruft diesen Thread zum Lesen der Datei ab. Wenn die Datei vollständig gelesen wurde, wird der Rest der button1_Click-Methode (aus dem GUI-Thread) mit dem Ergebnis aufgerufen. Beachten Sie, dass dies wahrscheinlich nicht die effizienteste Lösung ist, aber es ist wahrscheinlich die einfachste Änderung an Ihrem Code, die die GUI nicht blockiert.

+0

Arbeitete wie ein Zauber !!! Danke Mike, ich konnte 'Task.Factory.StartNew (() =>' Some Task ') auch auf andere Aufgaben anwenden, Danke nochmal :) –

+10

Während dies sicherlich die einfachste Lösung ist, wird es wahrscheinlich gut genug sein für eine einfache GUI-Anwendung verwendet es 'async' nicht zu seinem vollen Potential, weil es immer noch einen Thread blockiert. – svick

+0

Sie können Ihren Code auch verkürzen, indem Sie 'Task.Run()' verwenden. – svick

-3

Ich habe auch ein Problem in Ihrer Frage beschrieben. Ich habe es einfach einfacher gelöst, dass in früheren Antworten:

string[] values; 
StorageFolder folder = ApplicationData.Current.LocalFolder; // Put your location here. 
IList<string> lines = await FileIO.ReadLinesAsync(await folder.GetFileAsync("Words.txt");); 
lines.CopyTo(values, 0); 
+0

Woher kommen die Klassen 'ApplicationData' und' FileIO'? Sie scheinen nicht Teil des .Net Framework zu sein. 'ApplicationData' scheint aus dem [UWP Framework] zu stammen (https://docs.microsoft.com/en-us/uwp/api/windows.storage.applicationdata). Das bedeutet, dass Sie 'ApplicationData' nicht in einer" normalen ".net-Anwendung verwenden können. 'FileIO' existiert in der [VisualBasic-Assembly] (https://msdn.microsoft.com/en-us/library/microsoft.visualbasic.fileio.filesystem (v = vs.110) .aspx), hat dies aber nicht asynchrone Methoden, soweit ich das sehe, woher kommst du dann? – AndyJ

+0

@AndyJ, ja meine Lösung ist für UWP-Anwendungen. –