Подтвердить что ты не робот

Как Async Files.ReadAllLines и ждать результатов?

У меня есть следующий код,

    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, я пытаюсь использовать ключевые слова async и await в С# 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;
    }

Цель состоит в том, чтобы прочитать файл в async, а не в синхронизации, чтобы избежать замораживания приложения WPF.

Любая помощь приветствуется, спасибо!

4b9b3361

Ответ 1

ОБНОВЛЕНИЕ: асинхронные версии File.ReadAll[Lines|Bytes|Text], File.AppendAll[Lines|Text] и File.WriteAll[Lines|Bytes|Text] теперь объединены в .NET Core и поставляются с .NET Core 2.0. Они также включены в .NET Standard 2.1.

Использование Task.Run, который по сути является оболочкой для Task.Factory.StartNew, для асинхронных оболочек является запахом кода.

Если вы не хотите тратить поток ЦП с помощью функции блокировки, вам следует дождаться действительно асинхронного метода ввода-вывода, 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();
    }
}

Ответ 2

Используйте Stream.ReadAsync для асинхронного чтения файла,

private async void Button_Click(object sender, RoutedEventArgs e)
{
    string filename = @"c:\Temp\userinputlog.txt";
    byte[] result;

    using (FileStream SourceStream = File.Open(filename, FileMode.Open))
    {
        result = new byte[SourceStream.Length];
        await SourceStream.ReadAsync(result, 0, (int)SourceStream.Length);
    }

    UserInput.Text = System.Text.Encoding.ASCII.GetString(result);
}

Читать MSDN Stream.ReadAsync

Ответ 3

Я также столкнулся с проблемой, описанной в вашем вопросе. Я решил это проще, чем в предыдущих ответах:

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

Ответ 4

Попробуйте следующее:

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:

Вам не нужна попытка - наконец, чтобы это работало. Это действительно только одна строка, которую нужно изменить. Чтобы объяснить, как это работает: это порождает другой поток (фактически получает один из пула потоков) и получает этот поток для чтения файла. Когда файл завершит чтение, остаток метода button1_Click вызывается (из потока GUI) с результатом. Обратите внимание, что это, вероятно, не самое эффективное решение, но это, вероятно, самое простое изменение вашего кода, который не блокирует графический интерфейс.