c# await用法 - 如何异步Files.ReadAllLines并等待结果?




await教程 await详解 (4)

我有以下代码,

    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有很多单词,我读入s变量,我试图使用asyncawait C#5中的关键字使用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;
    }

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

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


Answers

更新File.ReadAll[Lines|Bytes|Text]File.AppendAll[Lines|Text]File.WriteAll[Lines|Bytes|Text]异步版本现已合并到.NET Core中 。 希望将这些方法移植回.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...
}

这将把整个文件作为string而不是List<string> 。 如果你需要换行,你可以在之后轻松拆分字符串,如下所示:

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

编辑 :这里有一些方法来实现与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();
    }
}

尝试这个:

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

编辑:

你不需要try-finally来实现这一点。 这真的只是你需要改变的一条线。 解释它是如何工作的:这会生成另一个线程(实际上从线程池中获取一个)并获取该线程来读取该文件。 当文件读完后,将调用button1_Click方法的其余部分(来自GUI线程)并显示结果。 请注意,这可能不是最有效的解决方案,但它可能是对代码的最简单更改,它不会阻止GUI。


我也遇到了你问题中描述的问题。 我在以前的答案中解决了它的简单性:

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

您可以使用XMLHttpRequest(没有flash和iframe依赖)在进行异步上载时传递其他参数和文件名。使用FormData附加附加参数值并发送上载请求。

function uploadButtonCLicked(){
    var input = document.querySelector('input[type="file"]')

    fetch('/url', {
      method: 'POST',
      body: input.files[0]
    }).then(res => res.json())   // you can do something with response
      .catch(error => console.error('Error:', error))
      .then(response => console.log('Success:', response));
}                               

此外,Syncfusion JavaScript UI文件上载仅使用事件参数为此方案提供解决方案。你可以找到的文档在这里和有关此控件的详细信息在这里输入链接的描述在这里







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