C# 异步流使用方法 C#如何使用IAsyncEnumerable

来源:这里教程网 时间:2026-02-21 17:42:20 作者:

什么是
IAsyncEnumerable<t></t>
,它和普通
IEnumerable<t></t>
有什么区别

IAsyncEnumerable<t></t>
是 C# 8.0 引入的异步流接口,用于按需、异步地生成或消费一系列元素。它不是一次性加载全部数据(像
IEnumerable<t></t>
那样可能触发同步延迟或阻塞),而是在每次
await foreach
迭代时,真正等待下一个元素就绪——适合数据库游标、HTTP 流式响应、实时日志拉取等场景。

关键区别在于: -

IEnumerable<t></t>
GetEnumerator()
返回同步迭代器,
MoveNext()
是同步调用; -
IAsyncEnumerable<t></t>
GetAsyncEnumerator()
返回
IAsyncEnumerator<t></t>
,其
MoveNextAsync()
ValueTask<bool></bool>
,可真正异步挂起; - 必须用
await foreach
消费,不能直接用
foreach
(编译器会报错)。

如何定义并返回
IAsyncEnumerable<t></t>

最常用方式是用

async yield return
编写本地异步迭代器方法。注意该方法必须返回
IAsyncEnumerable<t></t>
,且标记为
async

public static async IAsyncEnumerable<string> ReadLinesAsync(string path)
{
    await foreach (var line in File.ReadLinesAsync(path))
    {
        yield return line.Trim();
    }
}

几点实操提醒: -

yield return
async
方法中只允许出现在
IAsyncEnumerable<t></t>
IAsyncEnumerator<t></t>
返回类型的方法里; - 不支持在
try
块中
yield return
(但
await
可以); - 若需异常传播到消费者,直接抛出即可——
await foreach
会捕获并重新抛出; - 不要手动实现
IAsyncEnumerable<t></t>
,除非有特殊调度/生命周期控制需求。

await foreach
的正确写法与常见陷阱

消费端必须用

await foreach
,且所在方法需标记为
async
并返回
Task
ValueTask

public static async Task ProcessLogs()
{
    await foreach (var line in ReadLinesAsync("access.log"))
    {
        if (line.Contains("ERROR")) 
            Console.WriteLine(line);
    }
}

容易踩的坑: - 忘记加

await
:写成
foreach (var x in asyncSource)
会编译失败,提示“无法隐式转换”; - 在非
async
方法里调用:编译器报错 CS4032; - 混用
ConfigureAwait(false)
:目前
await foreach
不支持直接配置上下文,若需避免上下文捕获,应在迭代器内部的
await
上使用; - 提前退出循环(如
break
或异常)时,
DisposeAsync()
会被自动调用——但仅当迭代器实现了
IAsyncDisposable
(.NET 5+ 默认支持)。

性能与兼容性注意事项

IAsyncEnumerable<t></t>
在 .NET Core 3.0+ 原生支持,.NET Framework 不支持(即使装了 NuGet 包也无法获得语言级
await foreach
支持)。

实际使用中要注意: - 每次

MoveNextAsync()
调用都可能触发一次 await,高频小数据量场景(如内存 List 模拟)反而比同步迭代慢; - 如果底层数据源本身不支持真异步(比如包装了一个同步
IEnumerable<t></t>
),那只是“假异步”,仍会阻塞线程; - LINQ 操作如
Where
Select
有对应异步扩展方法(来自
System.Linq.Async
NuGet 包),但原生 LINQ to Objects 不支持
IAsyncEnumerable<t></t>
; - 调试时注意:VS 调试器对
await foreach
的断点支持良好,但内联异步 lambda 中的
yield return
可能无法逐行停靠。

真正需要异步流的地方,往往涉及 IO 边界或背压控制——别为了“看起来更现代”而强行替换已有的同步集合。

相关推荐