如何正确关闭Channel及使用多Channel?附代码问题排查及示例需求
问题根因
你的代码无法正确关闭Channel的核心原因有两个:
- 闭包捕获错误:for循环的迭代变量
k是整个循环作用域共享的,所有写入Task捕获的都是同一个k变量,等Task实际执行时k已经迭代到了10,访问长度为10的listStatus数组时会触发索引越界,导致状态标记逻辑完全失效,永远不会触发Channel的Complete操作。你定义了int kk = k但未实际使用,没有解决闭包问题。 - 自行实现的多写入任务完成判断逻辑冗余且存在线程安全隐患,不需要手动维护状态数组,用.NET内置的
Task.WhenAll就能简化实现。
单Channel场景修复代码
using System; using System.Collections.Generic; using System.Threading.Channels; using System.Threading.Tasks; namespace Ding.LearningNewThings { public class MultipleChannel { public static async Task RunMiltipleChannel() { List<Place> listPlace = Place.InitData(); Channel<Position> _dataChannel = Channel.CreateUnbounded<Position>(); // 单独存储所有写入任务 Task[] writeTasks = new Task[listPlace.Count]; for (int k = 0; k < listPlace.Count; k++) { // 捕获当前迭代的局部变量,解决闭包问题 var currentPlace = listPlace[k]; writeTasks[k] = Task.Run(async () => { int count = 0; Random r = new Random(Guid.NewGuid().GetHashCode()); // 避免多线程随机数种子重复 while (count < 10) { int id = r.Next(1, 1000); var position = new Position() { ID = id, Name = $"Position{id}", PlaceID = currentPlace.ID, PlaceName = currentPlace.Name }; Console.WriteLine($"WRITE: Position ID: {position.ID}, Position Name: {position.Name}"); await _dataChannel.Writer.WriteAsync(position); count++; } }); } // 写入任务全部完成后,统一关闭Channel var completeTask = Task.Run(async () => { await Task.WhenAll(writeTasks); _dataChannel.Writer.Complete(); Console.WriteLine("All write tasks completed, channel closed"); }); // 消费任务 var readTask = Task.Run(async () => { while (await _dataChannel.Reader.WaitToReadAsync()) { await Task.Delay(100); var data = await _dataChannel.Reader.ReadAsync(); Console.WriteLine($"READ: Position ID: {data.ID}, Position Name: {data.Name}"); } }); await Task.WhenAll(completeTask, readTask); } } public class Place { public int ID { get; set; } public string Name { get; set; } public static List<Place> InitData() { var listData = new List<Place>(); for (int i = 0; i < 10; i++) { listData.Add(new Place() { ID = i, Name = $"Place{i}" }); } return listData; } } public class Position { public int ID { get; set; } public int PlaceID { get; set; } public string PlaceName { get; set; } public string Name { get; set; } } }
每个Task独立Channel的实现方案
实现逻辑:每个写入任务对应创建独立的Channel,写入完成后直接关闭自己所属的Channel;如果需要统一消费所有Channel的数据,可以用System.Threading.Channels命名空间下的ChannelReader.Merge扩展方法合并多个读取器,合并后的读取器会在所有子Channel都关闭后自动结束读取。
实现示例:
public static async Task RunSeparateChannels() { List<Place> listPlace = Place.InitData(); List<Channel<Position>> allChannels = new List<Channel<Position>>(); List<Task> writeTasks = new List<Task>(); foreach (var place in listPlace) { // 为每个任务创建独立Channel var channel = Channel.CreateUnbounded<Position>(); allChannels.Add(channel); writeTasks.Add(Task.Run(async () => { int count = 0; Random r = new Random(Guid.NewGuid().GetHashCode()); while (count < 10) { int id = r.Next(1, 1000); var position = new Position() { ID = id, Name = $"Position{id}", PlaceID = place.ID, PlaceName = place.Name }; Console.WriteLine($"WRITE to channel for {place.Name}: Position ID: {position.ID}"); await channel.Writer.WriteAsync(position); count++; } // 当前任务写入完成,直接关闭自己的Channel channel.Writer.Complete(); Console.WriteLine($"Channel for {place.Name} closed"); })); } // 合并所有Channel的读取器,统一消费 var mergedReader = ChannelReader.Merge(allChannels.Select(c => c.Reader)); // 消费任务 var readTask = Task.Run(async () => { while (await mergedReader.WaitToReadAsync()) { await Task.Delay(100); var data = await mergedReader.ReadAsync(); Console.WriteLine($"READ from merged channel: Position ID: {data.ID}, Place: {data.PlaceName}"); } Console.WriteLine("All channels are closed, read task completed"); }); await Task.WhenAll(writeTasks.Concat(new[] { readTask })); }
内容的提问来源于stack exchange,提问作者quanchinhong
相关产品推荐
相关产品推荐

