如何异步Files.ReadAllLines并等待结果?

62 .net c# asynchronous async-await c#-5.0

我有以下代码,

    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;
    }
Run Code Online (Sandbox Code Playgroud)

Words.txt有很多单词,我读入s变量,我试图利用C#5中的asyncawait关键字,Async CTP Library所以WPF应用程序不会挂起.到目前为止,我有以下代码,

    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;
    }
Run Code Online (Sandbox Code Playgroud)

目标是以异步而不是同步方式读取文件,以避免冻结WPF应用程序.

任何帮助表示赞赏,谢谢!

khe*_*ang 125

UPDATE:的异步版本File.ReadAll[Lines|Bytes|Text],File.AppendAll[Lines|Text]File.WriteAll[Lines|Bytes|Text]现在已经被合并到.NET的核心.希望将这些方法移植回.NET Framework,Mono等,并将其转换为.NET Standard的未来版本.

对于异步包装Task.Run器来说Task.Factory.StartNew,使用它本身就是一个包装器,这是一种代码味道.

如果你不想通过阻塞函数浪费CPU线程,你应该等待一个真正的异步IO方法StreamReader.ReadToEndAsync,如下所示:

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

这将使整个文件成为一个string而不是一个List<string>.如果您需要使用线条,则可以在以后轻松拆分字符串,如下所示:

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

编辑:这里有一些方法可以实现相同的代码File.ReadAllLines,但是以一种真正的异步方式.代码基于File.ReadAllLines自身的实现:

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 这个重要的是使用Windows I/O端口来等待没有任何CPU线程,而另一个答案中的Task.Factory.StartNew/Task.Run方法浪费了CPU线程.这个答案的方法更有效率. (9认同)
  • 警告:File.OpenText 不会以异步模式打开文件。这会导致 ReadXxxxAsync 函数遵循不使用 IO Completion 的不同代码路径,并且效率较低。我不知道为什么微软选择让这个 API 如此难以正确使用。 (2认同)