我在测试C#8.0功能时遇到了IAsyncEnumerable。我从Anthony Chu(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>()
与async应用于上述foreach循环,以便它在读取时流式传输。
如何实现迭代器,以便可以使用yield return逐行读取?
答案 0 :(得分:1)
完全相同,但是没有异步工作负载,所以我们假装
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 Clearys评论以进行澄清
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
只允许同步I / O ; 异步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
}
答案 1 :(得分:1)
我进行了一些性能测试,看来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
的等待时间。例如,在我的PC上使用方法ReadToEndAsync
读取6 MB的文件将当前线程阻塞120毫秒,然后返回任务,然后仅用20毫秒完成任务。因此,我不确定使用这些方法是否有价值。通过使用同步API和一些Linq.Async:伪造异步要容易得多:
IAsyncEnumerable<string> lines = File.ReadLines("SomeFile.txt").ToAsyncEnumerable();