Прочитать текстовый файл с помощью IAsyncEnumerable

Я столкнулся с IAsyncEnumerable, когда тестировал функции C# 8.0. Я нашел замечательные примеры от Энтони Чу (https://anthonychu.ca/post/async-streams-dotnet-core-3-iasyncenumerable/). Это асинхронный поток и замена дляTask<IEnumerable<T>>

// Data Access Layer.
public async IAsyncEnumerable<Product> GetAllProducts()
{
    Container container = cosmosClient.GetContainer(DatabaseId, ContainerId);
    var iterator = container.GetItemQueryIterator<Product>("SELECT * FROM c");
    while (iterator.HasMoreResults)
    {
        foreach (var product in await iterator.ReadNextAsync())
        {
            yield return product;
        }
    }
}

// Usage
await foreach (var product in productsRepository.GetAllProducts())
{
    Console.WriteLine(product);
}

Мне интересно, можно ли это применить для чтения текстовых файлов, как показано ниже, которые читают файл построчно.

foreach (var line in File.ReadLines("Filename"))
{
    // ...process line.
}

Я действительно хочу знать, как применять асинхронность с IAsyncEnumerable<string>() в приведенный выше цикл foreach, чтобы он передавался во время чтения.

Как мне реализовать итератор, чтобы я мог использовать yield return для чтения построчно?

2 ответа

Решение

Точно так же, но нет асинхронной нагрузки, так что давайте представим

public async IAsyncEnumerable<string> SomeSortOfAwesomeness()
{
   foreach (var line in File.ReadLines("Filename.txt"))
   {
       // simulates an async workload, 
       // otherwise why would be using IAsyncEnumerable?
       // -- added due to popular demand 
       await Task.Delay(100);
       yield return line;
   }
}

или

Это просто обернутая рабочая нагрузка APM, Stephen Cleary см. Stephen Cleary комментариях Stephen Cleary.

public static async IAsyncEnumerable<string> SomeSortOfAwesomeness()
{
   using StreamReader reader = File.OpenText("Filename.txt");
   while(!reader.EndOfStream)
      yield return await reader.ReadLineAsync();
}

Применение

await foreach(var line in SomeSortOfAwesomeness())
{
   Console.WriteLine(line);
}

Обновление от Stephen Cleary

File.OpenTextк сожалению, позволяет только синхронный ввод-вывод; в API - интерфейсы асинхронных реализованы плохо в этом сценарии. Чтобы открыть настоящий асинхронный файл, вам нужно использоватьFileStream передача конструктора isAsync: true или FileOptions.Asynchronous.

ReadLineAsync в основном приводит к этому коду, как видите, это только Stream APM Begin а также End обернутые методы

private Task<Int32> BeginEndReadAsync(Byte[] buffer, Int32 offset, Int32 count)
{            
     return TaskFactory<Int32>.FromAsyncTrim(
                    this, new ReadWriteParameters { Buffer = buffer, Offset = offset, Count = count },
                    (stream, args, callback, state) => stream.BeginRead(args.Buffer, args.Offset, args.Count, callback, state), // cached by compiler
                    (stream, asyncResult) => stream.EndRead(asyncResult)); // cached by compiler
}

Я провел несколько тестов производительности, и кажется, что большой bufferSize полезно вместе с FileOptions.SequentialScan вариант.

public static async IAsyncEnumerable<string> ReadLinesAsync(string filePath)
{
    using var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read,
        FileShare.Read, 32768, FileOptions.Asynchronous | FileOptions.SequentialScan);
    using var reader = new StreamReader(stream);
    while (true)
    {
        var line = await reader.ReadLineAsync().ConfigureAwait(false);
        if (line == null) break;
        yield return line;
    }
}

Однако перечисление не является полностью асинхронным. Согласно моим экспериментам,xxxAsync методы StreamReader класс блокируют текущий поток на время, превышающее период ожидания Taskони вернулись. Например, чтение файла размером 6 МБ методом ReadToEndAsync на моем ПК блокирует текущий поток на 120 мс перед возвратом задачи, а затем задача завершается всего за 20 мс. Поэтому я не уверен, что использование этих методов имеет большую ценность. Подделать асинхронность намного проще, если использовать синхронные API вместе с некоторыми Linq.Async:

IAsyncEnumerable<string> lines = File.ReadLines("SomeFile.txt").ToAsyncEnumerable();
Другие вопросы по тегам