2012-10-31 14 views
43

Mam następujący kod,Jak asynchronizować Files.ReadAllLines i czekać na wyniki?

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 ma mnóstwo słów, które czytałem w zmiennej s, staram się wykorzystać async i await słów kluczowych w języku C# 5 Korzystanie Async CTP Library więc aplikacja WPF nie robi powiesić. Do tej pory mam następujący kod,

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

Celem jest, aby odczytać plik w asynchroniczny zamiast zsynchronizowane, aby uniknąć zamarzania WPF aplikacji.

Każda pomoc jest doceniana, dzięki!

+1

Co powiecie na początek, usuwając niepotrzebne wywołanie ToList(), które utworzy kopię tablicy łańcuchów? –

+2

@JbEvain - Aby być pedantycznym, 'ToList()' nie tylko kopiuje tablicę, tworzy 'List'. Bez dalszych informacji nie można założyć, że jest to niepotrzebne, ponieważ być może "' // coś z użyciem s' "wywołuje metody' List'. – Mike

Odpowiedz

80

UPDATE: asynchroniczny wersje File.ReadAll[Lines|Bytes|Text], File.AppendAll[Lines|Text] i File.WriteAll[Lines|Bytes|Text] zostały już merged into .NET Core. Miejmy nadzieję, że metody te zostaną przeniesione z powrotem do .NET Framework, Mono itd. I przetasowane do przyszłej wersji .NET Standard.

Używanie Task.Run, która zasadniczo jest opakowaniem dla Task.Factory.StartNew, dla pakowania asynchronicznego is a code smell.

Jeśli nie chcesz tracić wątek procesora za pomocą funkcji blokowania, należy oczekiwać prawdziwie asynchroniczną metodę IO StreamReader.ReadToEndAsync coś takiego:

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

ten dostanie cały plik jako string zamiast List<string>. Jeśli potrzebujesz linii zamiast, można łatwo podzielić ciąg potem tak:

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

EDIT: Oto kilka metod, aby osiągnąć ten sam kod co File.ReadAllLines, ale w prawdziwie sposób asynchroniczny. Kod jest oparty na realizacji File.ReadAllLines sobie:

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

Nie wiedziałem o tym dzięki khellang :) –

+6

To ważne, że używa portów portów we/wy systemu Windows, aby poczekać na to bez żadnych wątków procesora, podczas gdy podejście Task.Factory.StartNew/Task.Run w innej odpowiedzi powoduje utratę wątku procesora. Podejście to jest bardziej efektywne. –

+1

FYI; Zaproponowałem asynchroniczne wersje tych interfejsów API na stronie https://github.com/dotnet/corefx/issues/11220. Zobaczmy jak to idzie :) – khellang

-3

Spróbuj tego:

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:

Nie trzeba try-w końcu to zadziałało. To naprawdę tylko jedna linia, którą musisz zmienić. Aby wyjaśnić, jak to działa: Spowoduje to utworzenie kolejnego wątku (faktycznie pobiera go z puli wątków) i pobranie tego wątku w celu odczytania pliku. Po zakończeniu odczytu pliku wywoływana jest pozostała część metody button1_Click (z wątku GUI) z wynikiem. Zauważ, że nie jest to prawdopodobnie najbardziej wydajne rozwiązanie, ale prawdopodobnie jest to najprostsza zmiana w twoim kodzie, która nie blokuje GUI.

+0

Pracował jak urok !!! Dzięki Mike, mogłem zastosować 'Task.Factory.StartNew (() => 'Some Task')' również do innych zadań, Jeszcze raz dziękuję :) –

+10

Chociaż to z pewnością jest najprostszym rozwiązaniem i będzie najprawdopodobniej wystarczająco dobre dla prostej aplikacji GUI, nie używa ona "async" do pełnego potencjału, ponieważ nadal blokuje wątek. – svick

+0

Możesz również skrócić swój kod za pomocą 'Task.Run()'. – svick

-3

Wystąpił również problem opisany w pytaniu. Rozwiązałem go po prostu w prostszy sposób niż w poprzednich odpowiedziach:

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

Skąd pochodzą klasy "ApplicationData" i "FileIO"? Nie wydają się być częścią .Net Framework. "ApplicationData" wydaje się być z [UWP Framework] (https://docs.microsoft.com/en-us/uwp/api/windows.storage.applicationdata). Oznacza to, że nie można używać 'ApplicationData' w" normalnej "aplikacji .net. 'FileIO' istnieje w zespole [VisualBasic assembly] (https://msdn.microsoft.com/en-us/library/microsoft.visualbasic.fileio.filesystem (v = vs.110) .aspx), ale nie ma asynchroniczne metody, o ile widzę, więc skąd je bierzesz? – AndyJ

+0

@AndyJ, tak, moje rozwiązanie dotyczy aplikacji UWP. –

Powiązane problemy