简体   繁体   English

具有多线程的ConcurrentQueue

[英]ConcurrentQueue with multithreading

I am new to multi threading concepts. 我是多线程概念的新手。 I need to add certain number of strings to a queue and process them with multiple threads. 我需要将一定数量的字符串添加到队列中,并使用多个线程处理它们。 Using ConcurrentQueue which is thread safe. 使用线程安全的ConcurrentQueue

This is what I have tried. 这就是我尝试过的。 But all the items added into concurrent queue are not processed. 但是不会处理添加到并发队列中的所有项目。 only first 4 items are processed. 仅处理前4个项目。

class Program
{
    ConcurrentQueue<string> iQ = new ConcurrentQueue<string>();
    static void Main(string[] args)
    {
        new Program().run();
    }

    void run()
    {
        int threadCount = 4;
        Task[] workers = new Task[threadCount];

        for (int i = 0; i < threadCount; ++i)
        {
            int workerId = i;
            Task task = new Task(() => worker(workerId));
            workers[i] = task;
            task.Start();
        }

        for (int i = 0; i < 100; i++)
        {
            iQ.Enqueue("Item" + i);
        }

        Task.WaitAll(workers);
        Console.WriteLine("Done.");

        Console.ReadLine();
    }

    void worker(int workerId)
    {
        Console.WriteLine("Worker {0} is starting.", workerId);
        string op;
        if(iQ.TryDequeue(out op))
        {
            Console.WriteLine("Worker {0} is processing item {1}", workerId, op);
        }

        Console.WriteLine("Worker {0} is stopping.", workerId);
    }


}

There are a couple of issues with your implementation. 您的实现存在两个问题。 The first and obvious one is that the worker method only dequeues zero or one item and then stops: 第一个显而易见的是, worker方法仅使零或一个项目出队,然后停止:

    if(iQ.TryDequeue(out op))
    {
        Console.WriteLine("Worker {0} is processing item {1}", workerId, op);
    }

It should be: 它应该是:

    while(iQ.TryDequeue(out op))
    {
        Console.WriteLine("Worker {0} is processing item {1}", workerId, op);
    }

That however won't be enough to make your program work properly. 但是,这还不足以使您的程序正常运行。 If your workers are dequeueing faster than the main thread is enqueueing, they will stop while the main task is still enqueueing. 如果您的工作人员出队的速度快于主线程入队的速度,则他们将在主任务仍在排队时停止。 You need to signal the workers that they can stop. 您需要通知工人他们可以停车。 You can define a boolean variable that will be set to true once enqueueing is done: 您可以定义一个布尔变量,入队完成后将其设置为true

for (int i = 0; i < 100; i++)
{
    iQ.Enqueue("Item" + i);
}
Volatile.Write(ref doneEnqueueing, true);

The workers will check the value: 工人将检查该值:

void worker(int workerId)
{
    Console.WriteLine("Worker {0} is starting.", workerId);
    do {
        string op;
        while(iQ.TryDequeue(out op))
        {
            Console.WriteLine("Worker {0} is processing item {1}", workerId, op);
        }
        SpinWait.SpinUntil(() => Volatile.Read(ref doneEnqueueing) || (iQ.Count > 0));
    }
    while (!Volatile.Read(ref doneEnqueueing) || (iQ.Count > 0))
    Console.WriteLine("Worker {0} is stopping.", workerId);
}  

Your workers take one item out of the queue and then finish the work, just let them work till queue is empty. 您的工人从queue取出一件物品,然后完成工作,只是让他们工作直到queue为空。

Replace if in worker function with while if在worker函数中用while代替

void worker(int workerId)
{
    Console.WriteLine("Worker {0} is starting.", workerId);
    string op;
    while (iQ.TryDequeue(out op))
    {
        Console.WriteLine("Worker {0} is processing item {1}", workerId, op);
    }

    Console.WriteLine("Worker {0} is stopping.", workerId);
}

As you will run it you will see, that near all items will be processed by two workers. 当您运行它时,您会看到,几乎所有项目都将由两名工作人员处理。 Reason: your cpu has two cores, both are working and there is no "free tiem slot" to create new task. 原因:您的cpu有两个核心,两个都在工作,没有创建新任务的“可用tiem插槽”。 If you want to have all your 4 task to process items, you could add a delay to give your processor time to create anotehr tasks, something like: 如果要让所有4个任务处理项目,则可以增加延迟时间,以使处理器有时间创建anotehr任务,例如:

while (iQ.TryDequeue(out op))
{
    Console.WriteLine("Worker {0} is processing item {1}", workerId, op);
    Task.Delay(TimeSpan.FromMilliseconds(1)).Wait();
}

that gives you output, that you want: 可以为您提供所需的输出:

...
Worker 0 is processing item Item8
Worker 1 is processing item Item9
Worker 2 is processing item Item10
Worker 3 is processing item Item11
Worker 3 is processing item Item13
Worker 1 is processing item Item12
...

I've actually been working with ConcurrentQueue quite a bit recently and thought I'd share this. 实际上,我最近一直在与ConcurrentQueue合作,并认为我会分享这一点。 I've create a custom ConcurrentQueue called CQItems that has methods to build itself with given parameters. 我创建了一个名为CQItems的自定义ConcurrentQueue ,它具有使用给定参数构建自身的方法。 Internally, when you tell it to build x number of y items, it makes a Parallel.For call to the item constructors. 在内部,当您告诉它构建xy个项目时,它会创建一个Parallel.For调用项构造函数。 The benefit here is that when a method or function calls CQItems myCQ = CQItems.Generate(x, y) that call comes from the base application thread, meaning that nothing can look at the queue until it is finished building. 这样做的好处是,当方法或函数调用CQItems myCQ = CQItems.Generate(x, y) ,该调用来自基本应用程序线程,这意味着在完成构建之前,任何人都无法查看队列。 But internally to the queue class, it's building with threads, and is significantly quicker than just using a List<> or Queue<> . 但是在队列类内部,它是使用线程构建的,并且比仅使用List<>Queue<>快得多。 Mostly it's generating things out of thin air, but it also sometimes (based on params) is creating items from SQL - basically generating objects based on existing data. 通常,它是凭空产生的,但有时(基于参数)是根据SQL创建项目的-基本上是基于现有数据生成对象。 At any rate, these are the two methods in the CQItems class that can help with this: 无论如何,这是CQItems类中的两个方法可以帮助解决此问题:

public void Generate(int numberOfItems = 1, ItemOptions options = ItemOptions.NONE)
    {
        try
        {
            Type t = typeof(T);

            if (t == typeof(Item))
                throw new ArgumentException("Type " + t + " is not valid for generation.  Please contact QA.");

            else
                Parallel.For(0, numberOfItems, (i, loopState) =>
                {
                    try
                    {
                        GenerateItemByType(typeof(T), options);
                    }

                    catch
                    {
                        loopState.Stop();
                        throw;
                    }

                });
        }

        catch (AggregateException ae)
        {
            ae.Handle((x) =>
            {
                if (x is SQLNullResultsException)
                {
                    throw x;
                }
                else if (x is ImageNotTIFFException)
                {
                    throw x;
                }
                else
                {
                    throw x;
                }

                return true;
            });
        }

        catch
        {
            throw;
        }

        finally
        {
            ItemManager.Instance.Clear();
        }
    }

    private void GenerateItemByType(Type t, ItemOptions options = ItemOptions.NONE)
    {
        try
        {
            if (t == typeof(ItemA))
            {
                if ((options & ItemOptions.DUPLICATE) != 0)
                {
                    this.Enqueue(new ItemB(options));
                }
                else
                {
                    this.Enqueue(new ItemA(options));
                }
            }
            else if (t == typeof(ItemC))
            {
                this.Enqueue(new ItemC(options));
            }
        }

        catch
        {
            throw;
        }

        finally { }
    }

Some useful notes: 一些有用的注意事项:

Supplying the loopState variable in the Parallel.For() allows us to set the state to stop if an exception is caught. Parallel.For()提供loopState变量使我们可以将状态设置为在捕获到异常时停止 This is nice because if your loop is asked to do 1000 things, and the 5th iteration throws an exception, it's going to keep looping. 这很不错,因为如果您的循环被要求执行1000次操作,并且第5次迭代引发异常,它将继续循环。 You may want it to, but in my case an exception needs to exit the threaded loop. 您可能希望这样做,但是在我的情况下,需要退出线程循环。 You'll still end up with an AggregateException coming out of it (apparently, that's just what happens when threads throw exception). 您仍然最终会遇到AggregateException (显然,线程抛出异常时会发生这种情况)。 Parsing those out and only sending the first one can save a LOT of time and headaches trying to weed through a giant exception group where later exceptions may (or may not) have been caused due to the first anyway. 解析这些并仅发送第一个例外,可以节省大量时间和头痛,因为他们试图通过一个庞大的例外组进行除草,因为后来的例外可能(或可能没有)由于第一个例外而引起。

As for the rethrows, I try to add a catch statement for most expected types of exceptions even if I plan to just throw them up the stack anyway. 至于重新抛出问题,即使我打算无论如何都将它们抛出堆栈,我还是尝试为大多数期望的异常类型添加catch语句。 Part of this is for troubleshooting (being able to break on specific exceptions can be handy). 其中一部分是用于故障排除(能够轻松解决特定的异常情况)。 Part of it is because sometimes I want to be able to do other things, such as stopping the loop, changing or adding to the exception message, or in the case of breaking apart the AggregateException , only send one exception back up the stack rather than the whole aggregate. 部分原因是因为有时我希望能够做其他事情,例如停止循环,更改或添加到异常消息中,或者在分解AggregateException的情况下,仅将一个异常发送回堆栈,而不是整体。 Just a point of clarification for anyone who might be looking at this. 对于可能正在研究此问题的任何人来说,这只是一个澄清点。

Lastly, in case it's confusing, the Type(T) value is coming from my CQItems class itself: 最后,以防万一, Type(T)值来自我的CQItems类本身:

     public class CQItems<T> : ConcurrentQueue<Item>, IDisposable

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM