










C#中 IAsyncEnumerable 与 IAsyncEnumerator 的使用
1.支持异步的迭代器
是实现了接口 IAsyncEnumerator 的实例。它提供了一种异步方式以获取集合的下一个元素,进而允许“实现它的类或结构”可以异步地访问集合,并返回集合的元素。
接口 IAsyncEnumerable ,用于对外公开一个支持异步的枚举器。

//支持异步的枚举器 public interface IAsyncEnumerator<out T> : IAsyncDisposable { //获取枚举器当前位置的元素 T Current { get; } //导步地将枚举器前进到集合的下一个元素: //返回一个 ValueTask<bool>,通过 await运算符,可以追踪到枚举器是否成功推进到下一个元素; //如果枚举器已成功推进到下一个元素,返回 true; //如果枚举器已超过集合的末尾,返回 false。 ValueTask<bool> MoveNextAsync(); } //对外公开一个支持异步的枚举器 public interface IAsyncEnumerable<out T> { //获取支持异步的枚举器,cancellationToken: 一个 System.Threading.CancellationToken,可用于取消异步迭代。 IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = default); } //提供异步释放非托管资源的机制 public interface IAsyncDisposable { //异步释放或重置非托管资源; //返回一个 ValueTask,通过 await运算符,可以追踪到“释放或重置非托管资源”任务是否完成 ValueTask DisposeAsync(); }
View Code
(1)示例代码
用于实现一个支持异步的迭代器 Test2_:假定一个耗时的工作,它随机生成一个整数,如果合法,则读取数据并返回。

/// <summary> /// 支持异步的迭代器 /// </summary> public class Test2_ : IAsyncEnumerator<string> { private readonly string[] data = { " a", " b", " c" }; public async ValueTask<bool> MoveNextAsync() { var result = await Task.Run(() => { Thread.Sleep(500); var random = new Random(); int randomInt = random.Next(0, 5); bool valid = randomInt < data.Length; return (valid ? data[randomInt] : null, valid); }); _current = result.Item1; return result.valid; } private string _current = null!; public string Current { get { return _current; } } public ValueTask DisposeAsync() { return ValueTask.CompletedTask; } }
View Code
对示例代码的调用及其一次结果如下:

internal partial class Program { static void Main(string[] args) { Task.Run(() => { ToTest2_2(); }); Console.ReadKey(); } private static async void ToTest2_2() { var test2_ = new Test2_(); while (await test2_.MoveNextAsync()) { Console.WriteLine(test2_.Current); } } }
View Code

(2)对外公开一个支持异步的迭代器
则可以使用 foreach 进行访问,foreach 前须加关键字 await。

public IAsyncEnumerator<string> GetAsyncEnumerator(CancellationToken cancellationToken = default) { return this; }
View Code
foreach 进行访问及其一次结果如下:

private static async void ToTest2_3() { var test2_ = new Test2_(); await foreach (var item in test2_) { Console.WriteLine(item); } }
View Code

2.使用关键字 yield 简化实现迭代器
(1)开发者不用再实现属性 Current,以及不用实现方法 MoveNextAsync() 和 DisposeAsync() ,这些工作都交给了编译器,由编译器自动完成。

public class Test2__ { private readonly string[] data = { " a", " b", " c" }; public async IAsyncEnumerator<string> E() { var result = await Task.Run(() => { Thread.Sleep(500); var random = new Random(); int randomInt = random.Next(0, 5); bool valid = randomInt < data.Length; return (valid ? data[randomInt] : null, valid); }); if (result.valid) { yield return result.Item1; } } public IAsyncEnumerator<string> GetAsyncEnumerator(CancellationToken cancellationToken = default) { return E(); } } internal partial class Program { private static async void ToTest2_4() { for (int i = 0; i < 100; i++) { var test2__ = new Test2__(); await foreach (var item in test2__) { Console.WriteLine(item); } } } }
View Code
(2)甚至进一步简化实现,连方法 GetAsyncEnumerator() 都不用实现,一并交给了编译器:

public class Test2__1 { private readonly string[] data = { " a", " b", " c" }; public async IAsyncEnumerable<string> Y() { var result = await Task.Run(() => { Thread.Sleep(500); var random = new Random(); int randomInt = random.Next(0, 5); bool valid = randomInt < data.Length; return (valid ? data[randomInt] : null, valid); }); if (result.valid) { yield return result.Item1; } } } internal partial class Program { private static async void ToTest2_5() { for (int i = 0; i < 100; i++) { var test2__1 = new Test2__1(); await foreach (var item in test2__1.Y()) { Console.WriteLine(item); } } } }
View Code
(3)控制流流转方式:
控制流流转方式,结合下图进行说明:

总结:
相关的控制流流转,可以参考:《C#中关键字 yield 的使用》 中的“执行顺序”,和 《C#中关键字 async 和 await 的使用》中的“控制流流转”。
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。