Прочитать текстовый файл с помощью 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();