我在玩C#8.0预览版,无法使用IAsyncEnumerable
。
我尝试了以下
public static async IAsyncEnumerable<int> Get()
{
for(int i=0; i<10; i++)
{
await Task.Delay(100);
yield return i;
}
}
我最终使用了名为AsyncEnumerator
的Nuget程序包,但出现以下错误:
IAsyncEnumerable<int>
'不包含'GetAwaiter
'的定义,并且没有可访问的扩展方法'GetAwaiter
'接受类型为'IAsyncEnumerable<int>
'的第一个参数可以找到(您是否缺少using指令或程序集引用?)Program.Get()
'的主体不能是迭代器块,因为'IAsyncEnumerable<int>
'不是迭代器接口类型我在这里想念什么?
答案 0 :(得分:9)
这是编译器中的一个错误,可以通过添加几行代码found here来解决:
namespace System.Threading.Tasks
{
using System.Runtime.CompilerServices;
using System.Threading.Tasks.Sources;
internal struct ManualResetValueTaskSourceLogic<TResult>
{
private ManualResetValueTaskSourceCore<TResult> _core;
public ManualResetValueTaskSourceLogic(IStrongBox<ManualResetValueTaskSourceLogic<TResult>> parent) : this() { }
public short Version => _core.Version;
public TResult GetResult(short token) => _core.GetResult(token);
public ValueTaskSourceStatus GetStatus(short token) => _core.GetStatus(token);
public void OnCompleted(Action<object> continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags) => _core.OnCompleted(continuation, state, token, flags);
public void Reset() => _core.Reset();
public void SetResult(TResult result) => _core.SetResult(result);
public void SetException(Exception error) => _core.SetException(error);
}
}
namespace System.Runtime.CompilerServices
{
internal interface IStrongBox<T> { ref T Value { get; } }
}
正如Mads Torgersen在Take C# 8 for a spin中所述:
但是,如果您尝试编译和运行它,则会得到令人尴尬的错误。那是因为我们搞砸了一点,但没有使.NET Core 3.0和Visual Studio 2019的预览完美对齐。具体而言,有一种实现类型可以利用异步迭代器来实现,与编译器的期望有所不同。
您可以通过将一个单独的源文件添加到项目中来解决此问题,该文件包含this bridging code。再次编译,一切都应该正常工作。
更新
在异步迭代器中使用Enumerable.Range()
时,还会发现另一个错误。
问题GetNumbersAsync()
中的方法仅在两次迭代后结束:
static async Task Main(string[] args)
{
await foreach (var num in GetNumbersAsync())
{
Console.WriteLine(num);
}
}
private static async IAsyncEnumerable<int> GetNumbersAsync()
{
var nums = Enumerable.Range(0, 10);
foreach (var num in nums)
{
await Task.Delay(100);
yield return num;
}
}
仅打印:
0
1
使用数组甚至其他迭代器方法都不会发生这种情况:
private static async IAsyncEnumerable<int> GetNumbersAsync()
{
foreach (var num in counter(10))
{
await Task.Delay(100);
yield return num;
}
}
private static IEnumerable<int> counter(int count)
{
for(int i=0;i<count;i++)
{
yield return i;
}
}
这将打印预期的内容:
0
1
2
3
4
5
6
7
8
9
更新2
似乎也是一个已知的错误: Async-Streams: iteration stops early on Core
答案 1 :(得分:0)
关于使异步枚举工作所需的桥接代码,我几天前发布了一个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); // no more .ToArray()
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 Core 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;
}
}
}
请注意两个更改:
foreach
成为await foreach
nums
成为nums.ToAsync()
包装器尽可能轻巧,其实现基于以下类(请注意,ValueTask<T>
和IAsyncEnumerable<T>
强制使用IAsyncEnumerator<T>
允许一个常数foreach
的堆分配数:
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()
上调用IEnumerable
将您的foreach
变成await foreach
。