关于使异步可枚举工作所需的桥接代码,几天前我发布了一个nuget,它的作用是:
CSharp8Beta.AsyncIteratorPrerequisites.Unofficial
与人们普遍认为的相反,以下代码实际上产生了预期的结果:
private static async IAsyncEnumerable<int> GetNumbersAsync()
{
var nums = Enumerable.Range(0, 10).ToArray();
foreach (var num in nums)
{
await Task.Delay(100);
yield return num;
}
}
这是因为
IEnumerable<int>
被具体化为
int
数组。两次迭代后实际终止的是
IEnumerable<int>
本身就是这样:
var nums = Enumerable.Range(0, 10);
foreach (var num in nums) {
不过,尽管将查询转换为物化集合似乎是一个聪明的技巧,但并不总是希望缓冲整个序列(从而同时丢失内存和时间)。
考虑到表演,我发现
几乎
零分配包装器
IEnumerable
它会变成一个
IAsyncEnumerable
加用
await foreach
而不仅仅是
foreach
会避开这个问题。
我最近发布了一个新版本的nuget包,它现在包含一个名为
ToAsync<T>()
对于
IEnumerable<T>
一般来说,放在
System.Collections.Generic
就是这样。方法的签名是:
namespace System.Collections.Generic {
public static class EnumerableExtensions {
public static IAsyncEnumerable<T> ToAsync<T>(this IEnumerable<T> @this)
在将nuget包添加到.NET核心3项目后,可以这样使用它:
using System.Collections.Generic;
...
private static async IAsyncEnumerable<int> GetNumbersAsync() {
var nums = Enumerable.Range(0, 10);
await foreach (var num in nums.ToAsync()) {
await Task.Delay(100);
yield return num;
}
}
}
注意这两个变化:
-
前额
变成
等待前程
-
nums
成为
nums.ToAsync()
包装器尽可能轻,其实现基于以下类(请注意,使用
ValueTask<T>
执行
IAsyncEnumerable<T>
和
IAsyncEnumerator<T>
允许为每个
前额
):
public static class EnumerableExtensions {
public static IAsyncEnumerable<T> ToAsync<T>(this IEnumerable<T> @this) => new EnumerableAdapter<T>(@this);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IAsyncEnumerator<T> ToAsync<T>(this IEnumerator<T> @this) => new EnumeratorAdapter<T>(@this);
private sealed class EnumerableAdapter<T> : IAsyncEnumerable<T> {
private readonly IEnumerable<T> target;
public EnumerableAdapter(IEnumerable<T> target) => this.target = target;
public IAsyncEnumerator<T> GetAsyncEnumerator() => this.target.GetEnumerator().ToAsync();
}
private sealed class EnumeratorAdapter<T> : IAsyncEnumerator<T> {
private readonly IEnumerator<T> enumerator;
public EnumeratorAdapter(IEnumerator<T> enumerator) => this.enumerator = enumerator;
public ValueTask<bool> MoveNextAsync() => new ValueTask<bool>(this.enumerator.MoveNext());
public T Current => this.enumerator.Current;
public ValueTask DisposeAsync() {
this.enumerator.Dispose();
return new ValueTask();
}
}
}
总结一下:
-
能够编写异步生成器方法(
async IAsyncEnumerable<int> MyMethod() ...
)并使用异步可枚举项(
await foreach (var x in ...
)只需安装
NuGet
在你的项目中。
-
为了避免迭代过早停止,请确保
system.collections.generic
在你的
using
条款,调用
.ToAsync()
对你
可枚举的
转动你的
前额
变成一个
等待前程
.