多线程环境下按入队顺序打印队列内容的实现疑问
问题解答
你的代码无法实现严格按入队顺序打印所有元素的需求,仅能保证相同User.Id的任务串行执行,但不同Id的任务之间没有顺序约束,会出现乱序输出。
原因分析
- 任务调度无顺序性:在
ReadFromQueue方法中,你一次性将队列所有元素出队,然后为每个元素创建独立的Task交由线程池调度。线程池的任务执行顺序完全由系统资源分配决定,不遵循任务创建的先后顺序,因此不同Id的元素(比如第一个{1, "Single"}和第二个{2, "Single"})谁先执行是随机的。 - 同步范围有限:你使用的
SemaphoreSlim仅针对相同User.Id的任务做了串行控制,确保同一个Id的任务按入队顺序执行,但不同Id的任务之间没有任何同步机制,无法保证整体的入队顺序。
改进方案
根据你的需求,提供两种可行的改进方向:
方向一:严格按入队顺序执行任务(串行执行任务链)
如果要求PrintInOrder方法必须严格按入队顺序依次执行(前一个元素处理完成后再处理下一个),可以通过构建顺序任务链实现:
修改ReadFromQueue方法为异步版本:
internal async Task ReadFromQueueAsync() { while (usersQueue.Count > 0) { var user = usersQueue.Dequeue(); // 等待当前任务执行完成后,再处理下一个元素 await Task.Run(() => PrintInOrder(user)); } }
同时修改Main方法调用:
public static async Task Main() { var printUsersQueueInOrderMutlithreaded = new PrintUsersQueueInOrderMutlithreaded(); printUsersQueueInOrderMutlithreaded.PopulateQueue(); await printUsersQueueInOrderMutlithreaded.ReadFromQueueAsync(); Console.ReadLine(); }
这种方式保证了任务的执行顺序完全匹配入队顺序,每个任务完成后才会启动下一个。
方向二:并行处理任务,严格按入队顺序输出结果
如果允许任务并行执行(比如PrintInOrder内有耗时操作),但要求输出结果严格按入队顺序,可以通过为元素添加序号+并行处理后排序输出实现:
- 为
User类添加序号属性:
internal class User { public int Id { get; set; } public string MaritalStatus { get; set; } public int SequenceNumber { get; set; } // 新增序号属性 public override string ToString() { return $"Thread id {Thread.CurrentThread.ManagedThreadId,2}" + $" User id {Id,2} Marital status {MaritalStatus,10}"; } }
- 修改
PopulateQueue方法为元素赋值序号:
internal void PopulateQueue() { int sequence = 0; usersQueue.Enqueue(new User() { Id = 1, MaritalStatus = "Single", SequenceNumber = sequence++ }); usersQueue.Enqueue(new User() { Id = 2, MaritalStatus = "Single", SequenceNumber = sequence++ }); usersQueue.Enqueue(new User() { Id = 1, MaritalStatus = "Married", SequenceNumber = sequence++ }); usersQueue.Enqueue(new User() { Id = 1, MaritalStatus = "Divorced", SequenceNumber = sequence++ }); usersQueue.Enqueue(new User() { Id = 2, MaritalStatus = "Married", SequenceNumber = sequence++ }); usersQueue.Enqueue(new User() { Id = 2, MaritalStatus = "Divorced", SequenceNumber = sequence++ }); usersQueue.Enqueue(new User() { Id = 1, MaritalStatus = "Widow", SequenceNumber = sequence++ }); usersQueue.Enqueue(new User() { Id = 2, MaritalStatus = "Widow", SequenceNumber = sequence++ }); }
- 修改
ReadFromQueue方法实现并行处理+顺序输出:
internal async Task ReadFromQueueAsync() { var tasks = new List<Task<(int Sequence, string Output)>>(); while (usersQueue.Count > 0) { var user = usersQueue.Dequeue(); // 并行执行任务,保存序号和输出结果 tasks.Add(Task.Run(() => { // 这里可以执行耗时的处理逻辑 return (user.SequenceNumber, user.ToString()); })); } // 等待所有任务完成 var results = await Task.WhenAll(tasks); // 按序号排序后输出,保证顺序 foreach (var result in results.OrderBy(r => r.Sequence)) { Console.WriteLine(result.Output); } }
这种方式既利用了多线程并行处理提升效率,又能保证最终输出严格匹配入队顺序。
内容的提问来源于stack exchange,提问作者Lior Dadon
相关产品推荐
相关产品推荐

