2012-10-31 12 views
43

ho il seguente codice,Come sincronizzare file asincroni.ReadAllLines e attendere i risultati?

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 ha una tonnellata di parole che ho letto nella variabile s, sto cercando di fare uso di async e await parole chiave in C# 5 utilizzando Async CTP Library così l'applicazione WPF doesn togliti Finora ho il seguente codice,

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

L'obiettivo è quello di leggere il file in asincrono piuttosto che di sincronizzazione, per evitare il congelamento di WPF app.

Qualsiasi aiuto è apprezzato, grazie!

+1

Che dire se si inizia rimuovendo la chiamata non necessaria a ToList() che creerà una copia dell'array di stringhe? –

+2

@JbEvain - Per essere pedante, 'ToList()' non copia semplicemente l'array, ma crea un 'List'. Senza ulteriori informazioni non si può ritenere che non sia necessario, dal momento che forse "// fa qualcosa con s" "chiama i metodi' List'. – Mike

risposta

80

UPDATE: Asincrono versioni di File.ReadAll[Lines|Bytes|Text], File.AppendAll[Lines|Text] e File.WriteAll[Lines|Bytes|Text] sono ormai merged into .NET Core. Si spera che questi metodi vengano portati su .NET Framework, Mono ecc. E inseriti in una versione futura di .NET Standard.

Utilizzo di Task.Run, che è essenzialmente un wrapper per Task.Factory.StartNew, per wrapper asincroni is a code smell.

Se non si vuole perdere un filo di CPU utilizzando una funzione di blocco, si dovrebbe attendere un metodo veramente asincrona IO, StreamReader.ReadToEndAsync, in questo modo:

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

questo otterrà l'intero file come string anziché List<string>. Se avete bisogno di linee, invece, si potrebbe facilmente dividere la stringa in seguito, in questo modo:

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

EDIT: Ecco alcuni metodi per ottenere lo stesso codice come File.ReadAllLines, ma in un modo veramente asincrono. Il codice si basa sulla realizzazione di File.ReadAllLines stessa:

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

Non sapevo questo grazie khellang :) –

+6

Questo utilizza in modo importante le porte I/O di Windows per attendere questo senza NESSUN thread della CPU, mentre l'approccio Task.Factory.StartNew/Task.Run in un'altra risposta spreca un thread della CPU. L'approccio di questa risposta è più efficiente. –

+1

FYI; Ho proposto versioni asincrone di queste API all'indirizzo https://github.com/dotnet/corefx/issues/11220. Vediamo come va :) – khellang

-3

Prova questa:

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:

Non è necessario il try-finally per questo lavoro. È davvero solo la linea che devi cambiare. Per spiegare come funziona: questo genera un altro thread (ne riceve uno dal pool di thread) e ottiene quel thread per leggere il file. Al termine della lettura del file, il resto del metodo button1_Click viene chiamato (dal thread della GUI) con il risultato. Si noti che questa non è probabilmente la soluzione più efficiente, ma è probabilmente la modifica più semplice al codice che non blocca la GUI.

+0

Ha funzionato come un fascino !!! Grazie Mike, sono stato in grado di applicare 'Task.Factory.StartNew (() => 'Some Task')' anche ad altri task, grazie ancora :) –

+10

Mentre questa è sicuramente la soluzione più semplice e sarà probabilmente abbastanza buona per una semplice applicazione GUI, non usa 'async' al massimo del suo potenziale, perché blocca ancora un thread. – svick

+0

Inoltre, è possibile abbreviare il codice usando 'Task.Run()'. – svick

-3

Ho riscontrato anche un problema descritto nella domanda. L'ho risolto in modo più semplice nelle precedenti risposte:

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

Da dove provengono le classi 'ApplicationData' e' FileIO'? Non sembrano far parte del .Net Framework. 'ApplicationData' sembra provenire da [UWP Framework] (https://docs.microsoft.com/en-us/uwp/api/windows.storage.applicationdata). Ciò significa che non è possibile utilizzare 'ApplicationData' in un'applicazione" normale ".net. 'FileIO' esiste nell'assembly [VisualBasic] (https://msdn.microsoft.com/en-us/library/microsoft.visualbasic.fileio.filesystem (v = vs.110) .aspx), ma non ha metodi asincroni per quanto posso vedere, quindi da dove vieni? – AndyJ

+0

@AndyJ, sì la mia soluzione è per le applicazioni UWP. –

Problemi correlati