按顺序多线程打印队列内容

问题描述 投票:0回答:2

我希望能够按照他们推送的相同顺序从队列中读取和打印队列中的元素,例如:

(顺序是从左到右) 队列 -> [{1, "Single"},{2,"Single"}, {1,"Married"},{1,"Divorced"},{2,"Married"},{2,"Divorced" },{1,"寡妇"},{2,"寡妇"}]

我尝试通过以下代码解决它:

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp
{
    public class PrintUsersQueueInOrderMutlithreaded
    {
        public static void Main()
        {
            var printUsersQueueInOrderMutlithreaded =
                new PrintUsersQueueInOrderMutlithreaded();
            printUsersQueueInOrderMutlithreaded.PopulateQueue();
            printUsersQueueInOrderMutlithreaded.ReadFromQueue();

            Console.ReadLine();
        }

        private Dictionary<int, SemaphoreSlim> userLocks;
        private Queue<User> usersQueue;

        private string[] maritalStatus = { "Married", "Divorced", "Widow" };

        public PrintUsersQueueInOrderMutlithreaded()
        {
            usersQueue = new Queue<User>();
            userLocks = new Dictionary<int, SemaphoreSlim>();
        }

        internal void PopulateQueue()
        {
            usersQueue.Enqueue(new User() { Id = 1, MaritalStatus = "Single" });
            usersQueue.Enqueue(new User() { Id = 2, MaritalStatus = "Single" });
            usersQueue.Enqueue(new User() { Id = 1, MaritalStatus = "Married" });
            usersQueue.Enqueue(new User() { Id = 1, MaritalStatus = "Divorced" });
            usersQueue.Enqueue(new User() { Id = 2, MaritalStatus = "Married" });
            usersQueue.Enqueue(new User() { Id = 2, MaritalStatus = "Divorced" });
            usersQueue.Enqueue(new User() { Id = 1, MaritalStatus = "Widow" });
            usersQueue.Enqueue(new User() { Id = 2, MaritalStatus = "Widow" });
        }

        internal void ReadFromQueue()
        {
            List<Task> tasks = new List<Task>();
            while (usersQueue.Count > 0)
            {
                var user = usersQueue.Dequeue();
                tasks.Add(Task.Run(() => PrintInOrder(user)));
            }

            Task.WaitAll(tasks.ToArray());
        }

        private void PrintInOrder(User user)
        {
            SemaphoreSlim userLock;
            lock (userLocks)
            {
                if (!userLocks.ContainsKey(user.Id))
                {
                    userLocks[user.Id] = new SemaphoreSlim(1, 1);
                }
                userLock = userLocks[user.Id];
            }

            userLock.Wait();
            try
            {
                Console.WriteLine(user);
            }
            finally
            {
                userLock.Release();
            }
        }
    }

    internal class User
    {
        public int Id { get; set; }
        public string MaritalStatus { get; set; }

        public override string ToString()
        {
            return $"Thread id {Thread.CurrentThread.ManagedThreadId,2}" +
                $" User id {Id,2} Marital status {MaritalStatus,2}";
        }
    }
}

这让我们想知道是否可以控制

PrintInOrder
执行的顺序:\

谢谢!

c# multithreading concurrency queue locking
2个回答
1
投票

你做

Task.Run
的那一刻,你通过创建多个并行流使订单不确定 - 你cannot从中恢复;如果你想保留订单,你需要retain订单。但实际上,您的代码已经破坏了线程安全:您可以并发访问
usersQueue
not 线程安全对象。如果这是我,我会切换到像
Channel<User>
这样的东西,并让一个阅读器简单地从中出队(异步,或者甚至通过
await foreach
上的
.AsAsyncEnumerable()
从它),并让那个单一的阅读器到所有人写的。


0
投票

我假设这里的目标是具有相同id的用户应该维护顺序,但是对于不同id的用户则不需要维护顺序。

迄今为止最简单的解决方案就是同步打印所有用户。无论如何,控制台本质上是同步的,所以如果您所做的只是打印项目,那么创建任务的整个过程就毫无意义。 IE。只是做:

while (usersQueue.Count > 0)
{
    Console.WriteLine(usersQueue.Dequeue());
}

但是假设您想对用户进行一些重要的处理,而不仅仅是打印。所以你实际上会从一些并行处理中获得一些好处。

一般的做法是排队,保证一些项目按顺序处理,同时允许其他项目并发处理。有几种可能的方法可以做到这一点:

  1. 自己创建并发队列,任务在每个队列中循环。
  2. 使用 dataflow 创建管道。您可以根据需要使用过滤器和并发限制来配置管道。
  3. 在开始任务时,每个用户 Id 使用一个 LimitedConcurrencyTaskScheduler,限制为一个。
© www.soinside.com 2019 - 2024. All rights reserved.