ConcurrentQueue与多线程

时间:2017-01-04 08:58:24

标签: c# multithreading concurrent-queue

我是多线程概念的新手。我需要将一定数量的字符串添加到队列中并使用多个线程处理它们。使用线程安全的ConcurrentQueue

这是我尝试过的。但是不会处理添加到并发队列中的所有项目。只处理前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);
    }


}

3 个答案:

答案 0 :(得分:5)

您的实施存在一些问题。第一个也是显而易见的是status = 0方法只将零个或一个项目出列然后停止:

worker

应该是:

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

然而,这不足以使您的程序正常运行。如果你的工人出列的速度比主线程入队的速度快,那么当主要任务仍然排队时它们会停止。你需要告诉工人他们可以停下来。您可以定义一个布尔变量,一旦排队完成,将设置为 while(iQ.TryDequeue(out op)) { Console.WriteLine("Worker {0} is processing item {1}", workerId, op); }

true

工人将检查价值:

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

答案 1 :(得分:3)

您的工作人员从queue中取出一件物品,然后完成工作,让它们工作直到queue为空。

使用if

替换辅助功能中的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);
}

正如您将运行它,您将看到,所有物品附近将由两名工人处理。原因:你的cpu有两个内核,两个都工作,没有“free tiem slot”来创建新任务。如果你想要处理所有4个任务来处理项目,你可以添加一个延迟,让你的处理器有时间来创建一些任务,例如:

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

为您提供所需的输出:

...
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
...

答案 2 :(得分:0)

我最近实际上一直在与ConcurrentQueue一起工作,并认为我会分享这一点。我创建了一个名为ConcurrentQueue的自定义CQItems,它具有使用给定参数构建自身的方法。在内部,当您告诉它构建 x y 个项目时,它将对项目构造函数进行Parallel.For调用。这样做的好处是,当方法或函数调用CQItems myCQ = CQItems.Generate(x, y)时,该调用来自基本应用程序线程,这意味着在完成队列构建之前,任何人都无法查看队列。但是在队列类内部,它是使用线程构建的,并且比仅使用List<>Queue<>快得多。通常,它是凭空产生的,但有时(基于参数)是根据SQL创建项目的-基本上是基于现有数据生成对象。无论如何,这些是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 { }
    }

一些有用的注释:

loopState中提供Parallel.For()变量使我们可以在捕获到异常的情况下将状态设置为 stop 。这很不错,因为如果您的循环被要求执行1000次操作,并且第5次迭代引发异常,它将继续循环。您可能希望这样做,但是在我的情况下,需要退出线程循环。您仍然会得到一个AggregateException(显然,线程抛出异常时会发生这种情况)。解析这些内容并只发送第一个例外,可以节省大量时间和头痛,因为他们试图通过一个庞大的例外组进行除草,因为后来的例外可能(或可能没有)由于第一个例外而引起。

对于重新抛出,即使我打算无论如何都将它们抛出堆栈,我还是尝试为大多数期望的异常类型添加catch语句。其中一部分是用于故障排除(能够轻松解决特定的异常情况)。部分原因是因为有时我希望能够做其他事情,例如停止循环,更改或添加异常消息,或者在分解AggregateException的情况下,仅发送一个异常备份堆栈而不是整个集合。对于可能正在查看此内容的任何人来说,这只是一个澄清点。

最后,以防万一,Type(T)值来自我的CQItems类本身:

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