惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园_首页
量子位
D
DataBreaches.Net
博客园 - 司徒正美
J
Java Code Geeks
博客园 - 【当耐特】
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
aimingoo的专栏
aimingoo的专栏
B
Blog
The Cloudflare Blog
D
Docker
I
InfoQ
爱范儿
爱范儿
MongoDB | Blog
MongoDB | Blog
腾讯CDC
月光博客
月光博客
Hugging Face - Blog
Hugging Face - Blog
Microsoft Azure Blog
Microsoft Azure Blog
Vercel News
Vercel News
阮一峰的网络日志
阮一峰的网络日志
小众软件
小众软件
S
SegmentFault 最新的问题
GbyAI
GbyAI
有赞技术团队
有赞技术团队

博客园 - Tlink

浅谈对上拉电阻的理解 电子积木 光敏电阻与光敏电阻模块:从原理到实战应用 电子小白:什么是PCB(印刷电路板) 电子小白:万用表使用场景 电子小白的枕边书:电子学(The Art of Electronics) 电子小白:光耦到底是什么? 数码管与LED点阵:电子世界的“像素屏幕” 熟悉电子元器件之后,电子小白下一步该怎么走? 电子小白:什么是运算放大器(运放) 电子小白入门:什么是稳压芯片? 电子小白入门之NE555 电子小白的工具三件套:面包板、杜邦线、万能板 电子小白入门:步进电机以及驱动器到底是个啥? 电子小白之集成电路 AI新时代真的来了,不管愿意还是不愿意 avalonia在国产芯片瑞芯微RK3588这么容易就跑起来了? 电子小白之晶振 电子小白之电感:通过无线充电带你彻底搞懂电感 电子小白之继电器 电子小白之MOS管 电子小白之三极管 电容是什么?一个“快充快放”的微型充电宝 电子小白之二极管 电压与电流之间的关系 电子电路中的“心脏”:电源 从付费软件到自主开发:我用AI和FFmpeg实现了一个录屏工具 新手小白学电路
C# Channel 实战:有界队列与 `DropOldest` 策略解析
Tlink · 2026-08-28 · via 博客园 - Tlink

在并发编程中,高效的数据传递与缓冲是常用需求。.NET 的 System.Threading.Channels 提供了线程安全的“生产者‑消费者”队列。本文通过一个简单示例,演示如何创建有界通道、配置丢弃最旧元素策略,并展示其运行效果。


一、背景:什么是 Channel?

Channel<T> 是 .NET Core / .NET 5+ 引入的异步数据流库,用于在多个生产者与消费者之间安全地交换数据。它类似于 BlockingCollection<T>,但基于 async/await 设计,天然支持异步读写,性能更优,且提供了丰富的配置选项。

  • 有界(Bounded):设定最大容量,防止内存无限增长。
  • 无界(Unbounded):不限制容量,但需注意内存压力。

本次我们聚焦有界通道,并演示其中一种“满则丢弃”的策略。


二、创建有界通道的代码片段

const int CHANNEL_CAPACITY = 2048;
Channel<List<DataRecord>> _channel;

_channel = Channel.CreateBounded<List<DataRecord>>(
    new BoundedChannelOptions(CHANNEL_CAPACITY)
    {
        FullMode = BoundedChannelFullMode.DropOldest,
        SingleReader = true,
        SingleWriter = false
    });

参数详解

参数 含义
CHANNEL_CAPACITY 通道最多缓存 2048 个元素(每个元素是一个 List<DataRecord>)。
FullMode 当通道已满时的行为:
DropOldest – 丢弃队列中最旧的元素,腾出空间写入新元素。
DropNewest – 丢弃新元素。
Wait – 异步阻塞直到有空间。
SingleReader true 表示只有一个消费者,可优化内部锁开销。
SingleWriter false 表示允许多个生产者并发写入(此处保留默认多写者)。

这种配置特别适合实时数据流场景,比如监控日志、传感器数据,我们更关心最新信息,可以容忍丢失部分旧数据。


三、完整示例(简化数据类型)

为了便于理解,我们将数据类型改为 string,并模拟一个快速生产、慢速消费的过程。

3.1 完整代码

using System;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;

class ChannelDropOldestDemo
{
    static async Task Main()
    {
        const int CAPACITY = 2048;

        // 1. 配置并创建通道
        var channel = Channel.CreateBounded<string>(
            new BoundedChannelOptions(CAPACITY)
            {
                FullMode = BoundedChannelFullMode.DropOldest,
                SingleReader = true,
                SingleWriter = false   // 尽管只有一个生产者,设为 false 不影响
            });

        var writer = channel.Writer;
        var reader = channel.Reader;

        // 2. 生产者:快速生产 10000 条消息
        _ = Task.Run(async () =>
        {
            for (int i = 0; i < 10_000; i++)
            {
                await writer.WriteAsync($"Message-{i:D4}");
                // 无延迟,瞬间填满通道
            }
            writer.Complete();  // 通知消费者:不再有新数据
            Console.WriteLine("生产者完成写入");
        });

        // 3. 消费者:慢速处理(每 100ms 读一条)
        Console.WriteLine("开始消费(每 100ms 读一条)...");
        int count = 0;
        await foreach (var msg in reader.ReadAllAsync())
        {
            Console.WriteLine($"[{++count,4}] {msg}");
            await Task.Delay(100);  // 模拟耗时操作
        }

        Console.WriteLine($"共消费 {count} 条消息");
    }
}

3.2 运行环境

  • .NET 6 / 7 / 8 或 .NET Core 3.1+
  • 需在项目文件中添加 System.Threading.Channels 包(若未默认包含)

四、运行结果与行为分析

由于生产者瞬间产生 10000 条消息,而消费者每 100ms 只消费 1 条,通道容量只有 2048,因此很快就会被填满。后续写入时,DropOldest 会不断丢弃队列头部(最早的消息),使队列中始终保留最新写入的 2048 条

最终消费者读到的消息编号会跳跃——中间大量旧消息被丢弃,只读到接近末尾的消息(例如从 5000 多开始),总消费数量大约等于通道容量(2048 左右)。

输出示意:

开始消费(每 100ms 读一条)...
[   1] Message-0000
[   2] Message-0001
...
[  20] Message-0019
[  21] Message-5098   // 跳跃!中间消息被丢弃
[  22] Message-5099
...
[2048] Message-9999
生产者完成写入
共消费 2048 条消息

注意:实际消费的起始编号和总数可能因线程调度略有浮动,但总体趋势相同。


五、使用建议与注意事项

  1. 适用场景

    • 日志采样、实时指标、事件流,允许丢失旧数据以保障最新数据。
    • 生产者速率远高于消费者,且不希望生产者阻塞时。
  2. 如果业务不允许丢数据
    请将 FullMode 改为 BoundedChannelFullMode.Wait,此时 WriteAsync 会异步等待,直到通道有空间。

  3. SingleReaderSingleWriter 的设定

    • 必须与实际读写并发数一致。若 SingleReader=true 但有多个消费者,会抛出异常。
    • 若实际只有一个生产者,建议设为 true 以提升性能。
  4. 通道完成(Complete)
    生产者调用 Complete() 后,消费者读取完剩余元素后,ReadAllAsync() 会正常结束,避免无限等待。

  5. 异步与同步读写

    • 异步方法:WriteAsync / ReadAsync
    • 同步方法:TryWrite / TryRead(适合高性能但需处理失败情况)

六、总结

System.Threading.Channels 是 .NET 中处理异步数据流的有力工具。通过配置有界容量和满时策略,我们可以灵活控制缓存行为和背压机制。本文重点介绍了 DropOldest 模式,并给出了一个直观的示例,帮助理解其运行时效果。

在实际项目中,您可以根据业务需求选择合适的 FullMode,并善用 SingleReader/SingleWriter 优化性能。异步编程与通道的结合,能让您轻松构建高吞吐、低延迟的数据管道。


相关资源

本文仅作入门引导,更多高级用法(如多生产者/消费者、异常处理等)可参考官方资料。本文示例代码由AI编写。