C# 中 的 IAsyncEnumerable

一、IAsyncEnumerable<T> 是什么?

一句话解释
它是一种异步数据流,让你可以像处理列表一样逐个处理数据,但数据是按需异步生成的。

类比生活场景
想象你在餐厅点菜:

  • 同步枚举(IEnumerable:服务员把整桌菜都做好端上来,你才能开始吃。
  • 异步枚举(IAsyncEnumerable:服务员做好一道菜就端上来一道,你可以边吃边等下一道。

二、为什么需要它?

典型场景

  1. 处理大数据:比如读取10GB的日志文件,不用一次性加载到内存。
  2. 远程数据:比如从API分页获取数据,不用等所有页都下载完。
  3. 实时数据流:比如监控传感器数据,数据是持续产生的。

三、如何使用?

1. 基本语法

// 定义一个异步生成数据的方法
async IAsyncEnumerable<int> GenerateNumbersAsync()
{
    for (int i = 0; i < 5; i++)
    {
        await Task.Delay(100); // 模拟异步操作(比如网络请求)
        yield return i;        // 返回一个数据项
    }
}

// 消费这个异步数据流
await foreach (var number in GenerateNumbersAsync())
{
    Console.WriteLine(number); // 逐个处理数据,不用等全部生成
}

关键语法点

  • async IAsyncEnumerable<T>:方法返回类型,表示这是一个异步数据流。
  • yield return:每次产生一个数据项,而不是一次性返回整个列表。
  • await foreach:异步遍历数据流,每次等待一个数据项就绪。

2. 实际例子:从文件异步读取数据

// 读取大文件的每一行,不用一次性加载整个文件
async IAsyncEnumerable<string> ReadLinesAsync(string filePath)
{
    await using var reader = new StreamReader(filePath);
    string line;
    
    while ((line = await reader.ReadLineAsync()) != null)
    {
        yield return line; // 读到一行就返回一行
    }
}

// 使用方式
await foreach (var line in ReadLinesAsync("large_file.txt"))
{
    Console.WriteLine($"处理行:{line.Length} 字符");
}

3. 处理异步API请求(分页数据)

假设你要从API获取用户列表(每页10个用户):

async IAsyncEnumerable<User> GetUsersAsync()
{
    int page = 1;
    List<User> currentPage;
    
    do
    {
        // 模拟API请求,每次获取一页数据
        currentPage = await FetchUsersFromApiAsync(page);
        page++;
        
        // 把当前页的用户逐个返回
        foreach (var user in currentPage)
        {
            yield return user;
        }
        
    } while (currentPage.Count > 0); // 还有下一页数据
}

// 使用方式
await foreach (var user in GetUsersAsync())
{
    Console.WriteLine($"用户:{user.Name}");
}

四、常见问题

1. 它和 Task<List<T>> 有什么区别?

  • Task<List<T>>:等待所有数据加载完成,返回完整列表(适合小数据)。
  • IAsyncEnumerable<T>:边加载边处理,适合大数据或实时数据流。

2. 如何取消异步枚举?

可以传入 CancellationToken

async IAsyncEnumerable<int> GenerateNumbersAsync(
    [EnumeratorCancellation] CancellationToken token = default)
{
    for (int i = 0; i < 1000; i++)
    {
        token.ThrowIfCancellationRequested(); // 检查是否取消
        await Task.Delay(100, token);
        yield return i;
    }
}

// 使用时可以取消
var cts = new CancellationTokenSource();
cts.CancelAfter(500); // 500ms后自动取消

await foreach (var number in GenerateNumbersAsync(cts.Token))
{
    Console.WriteLine(number);
}

五、总结

核心思想
IAsyncEnumerable<T> 让你可以异步地逐个处理数据,而不是等待所有数据准备好。这在处理大数据、远程数据或实时数据流时非常有用。

使用步骤

  1. 定义异步生成器:用 async IAsyncEnumerable<T>yield return 返回数据。
  2. 消费数据流:用 await foreach 逐个处理数据。
  3. 按需添加取消逻辑:通过 CancellationToken 控制异步操作。

✅ 用法结构总结一下:

🔹 定义阶段(生产数据)

public async IAsyncEnumerable<T> GetDataAsync(
    [EnumeratorCancellation] CancellationToken token = default)
{
    while (/* 有更多数据 */)
    {
        token.ThrowIfCancellationRequested(); // 取消支持
        await Task.Delay(100);               // 异步等待/IO操作
        yield return someData;               // 返回数据
    }
}

🔹 使用阶段(消费数据)

var cts = new CancellationTokenSource();
cts.CancelAfter(5000); // 或者在外部通过按钮控制取消

await foreach (var item in GetDataAsync(cts.Token))
{
    Console.WriteLine(item);
}

🔁 替换为你自己的类型举例

假设你定义了一个结构体:

public struct ChannelSample
{
    public DateTime Timestamp { get; set; }
    public float Value { get; set; }
}

然后你可以这么用:

public async IAsyncEnumerable<ChannelSample> ReadSamplesAsync(
    [EnumeratorCancellation] CancellationToken token = default)
{
    for (int i = 0; i < 1000; i++)
    {
        token.ThrowIfCancellationRequested();
        await Task.Delay(10, token); // 模拟采集

        yield return new ChannelSample
        {
            Timestamp = DateTime.Now,
            Value = i * 0.1f
        };
    }
}

消费它:

await foreach (var sample in ReadSamplesAsync(cts.Token))
{
    Console.WriteLine($"{sample.Timestamp}: {sample.Value}");
}

❗关于 [EnumeratorCancellation] 参数说明

这不是必须的,但推荐加上(尤其在 .NET Core 3.0+ / .NET 5+ 中),否则取消令牌可能不会正常生效。

  • 它告诉编译器/运行时:“这个 token 是给异步枚举器用的”
  • 否则你只能在调用时显式用 .WithCancellation(token) 来加 token

例子对比:

// 推荐方式(有 EnumeratorCancellation)
await foreach (var item in GetDataAsync(token)) { ... }

// 不加时要这样
await foreach (var item in GetDataAsync().WithCancellation(token)) { ... }

🧪 总结一下关键点

要素 说明
async IAsyncEnumerable<T> 定义一个异步生成器
await foreach 异步逐条读取
yield return 一条一条产生数据
[EnumeratorCancellation] 支持 await foreach 的取消
CancellationToken 控制中止,如用户点击“停止采集”
posted @ 2025-07-08 22:37  青云Zeo  阅读(108)  评论(0)    收藏  举报