当前位置:首页 > C# > 正文

掌握 C# 异步枚举:自定义实现详解(从零开始构建 IAsyncEnumerable 异步流)

在现代 C# 开发中,C#异步枚举(也称为异步流)是一种强大的特性,它允许我们在不阻塞主线程的情况下逐个获取数据项。这项功能自 C# 8.0 起引入,核心接口是 IAsyncEnumerable<T>。本文将手把手教你如何自定义异步迭代器,即使你是编程小白,也能轻松理解并应用。

掌握 C# 异步枚举:自定义实现详解(从零开始构建 IAsyncEnumerable 异步流) C#异步枚举  异步流 自定义异步迭代器 第1张

什么是 IAsyncEnumerable?

IAsyncEnumerable<T> 是 .NET 中用于表示异步可枚举序列的接口。与传统的 IEnumerable<T> 不同,它支持在每次获取下一个元素时执行异步操作(如网络请求、数据库查询等),而不会阻塞调用线程。

使用 await foreach 语句可以遍历 IAsyncEnumerable<T>,这是 C# 8.0 引入的新语法糖。

基础示例:使用 async yield return

最简单的自定义 C#异步枚举方式是使用 async IAsyncEnumerable<T> 方法配合 yield return

using System;using System.Collections.Generic;using System.Threading.Tasks;public static class SimpleAsyncEnumerator{    public static async IAsyncEnumerable<int> CountToFiveAsync()    {        for (int i = 1; i <= 5; i++)        {            await Task.Delay(500); // 模拟异步操作            yield return i;        }    }}// 使用示例public static async Task Main(){    await foreach (var number in SimpleAsyncEnumerator.CountToFiveAsync())    {        Console.WriteLine(number);    }}

这段代码每 500 毫秒输出一个数字,全程不阻塞主线程。这是构建异步流的最常用方式。

进阶:手动实现 IAsyncEnumerable<T>

有时你需要更精细地控制枚举行为(例如处理取消令牌、资源释放等),这时就需要手动实现 IAsyncEnumerable<T>IAsyncEnumerator<T> 接口。

下面是一个完整的自定义自定义异步迭代器示例:

using System;using System.Collections.Generic;using System.Threading;using System.Threading.Tasks;public class CountdownAsyncEnumerable : IAsyncEnumerable<int>{    private readonly int _start;    public CountdownAsyncEnumerable(int start)    {        _start = start;    }    public IAsyncEnumerator<int> GetAsyncEnumerator(        CancellationToken cancellationToken = default)    {        return new CountdownAsyncEnumerator(_start, cancellationToken);    }}public class CountdownAsyncEnumerator : IAsyncEnumerator<int>{    private int _current;    private readonly CancellationToken _cancellationToken;    public CountdownAsyncEnumerator(int start, CancellationToken cancellationToken)    {        _current = start + 1; // 初始值设为 start+1,第一次 MoveNextAsync 会减 1        _cancellationToken = cancellationToken;    }    public int Current { get; private set; }    public async ValueTask<bool> MoveNextAsync()    {        _cancellationToken.ThrowIfCancellationRequested();        if (_current <= 0)            return false;        await Task.Delay(300, _cancellationToken); // 模拟异步延迟        _current--;        Current = _current;        return true;    }    public ValueTask DisposeAsync()    {        // 如果有非托管资源或需要异步清理,可在此处处理        return ValueTask.CompletedTask;    }}// 使用示例public static async Task Main(){    var cts = new CancellationTokenSource();    cts.CancelAfter(TimeSpan.FromSeconds(2)); // 2 秒后自动取消    try    {        var countdown = new CountdownAsyncEnumerable(5);        await foreach (var num in countdown.WithCancellation(cts.Token))        {            Console.WriteLine($"Countdown: {num}");        }    }    catch (OperationCanceledException)    {        Console.WriteLine("Countdown was canceled.");    }}

在这个例子中,我们实现了完整的 IAsyncEnumerable<int>IAsyncEnumerator<int>。注意以下几点:

  • MoveNextAsync 返回 ValueTask<bool>,表示是否还有下一个元素。
  • 支持 CancellationToken,可在异步操作中响应取消请求。
  • 使用 .WithCancellation(token) 扩展方法将取消令牌传递给枚举器。

何时使用自定义实现?

大多数情况下,使用 async IAsyncEnumerable<T> + yield return 就足够了。但当你需要:

  • 精确控制资源生命周期(如数据库连接、文件句柄)
  • 实现复杂的取消逻辑
  • 复用枚举器实例
  • 与第三方异步 API 深度集成

此时,手动实现 IAsyncEnumerable<T>IAsyncEnumerator<T> 就显得非常有价值。

总结

通过本文,你已经掌握了 C#异步枚举 的两种实现方式:简单场景使用 yield return,复杂场景手动实现接口。无论哪种方式,都能构建高效的异步流,提升应用程序的响应性和性能。

记住关键点:使用 await foreach 遍历,支持取消令牌,合理释放资源。现在,你可以自信地在项目中使用 自定义异步迭代器 了!