Pinkie Pie
Pinkie Pie

Reputation: 61

Concurrency vs Normal Collection?

so i have a question about the System.Collections.Concurrent

I saw that the Concurrent is acctually a safe thread collection, but in wich cases it can be helpfull?

I made 2 examples and the result are the same

First the ConcurrentQueue:

    static ConcurrentQueue<int> queue = new ConcurrentQueue<int>();
    private static readonly object obj = new object();
    static int i = 0;
    static int Num = 0;
    static void Run(object loopNum)
    {
        lock (obj)
        {
            for (int N = 0; N < 10; N++)
            {
                queue.Enqueue (i);
                Thread.Sleep(250);
                queue.TryDequeue(out Num);
                Console.WriteLine($"{Num} Added! in {loopNum} Loop, ThreadID: [{Thread.CurrentThread.ManagedThreadId}]");
                i++;
            }
        }
    }

And now the normal Queue:

    static Queue<int> queue = new Queue<int>();
    private static readonly object obj = new object();
    static int i = 0;

    static void Run(object loopNum)
    {
        lock (obj)
        {
            for (int N = 0; N < 10; N++)
            {
                queue.Enqueue (i);
                Thread.Sleep(250);
                Console.WriteLine($"{queue.Dequeue()} Added! in {loopNum} Loop, ThreadID: [{Thread.CurrentThread.ManagedThreadId}]");
                i++;
            }
        }
    }

Main:

    static void Main()
    {
        Thread[] Th = new Thread[] { new Thread(Run), new Thread(Run) };
        Th[0].Start("First");
        Th[1].Start("Second");


        Console.ReadKey();
    }

The result are the same

Sure, it got some diffrent methods like TryDequeue And a few more, but what it really helpfull for?

Any help will be very appriciated :)

Upvotes: 1

Views: 93

Answers (4)

Pinkie Pie
Pinkie Pie

Reputation: 61

Thank you everyone for all your answers, really helped me out, i appriciate it alot.

By the way Matthew Watson, your example sometimes give's an exception and sometime's isnt, i made a better example, but yeah i get the point.

    private static void Main()
    {
        var queue1 = new ConcurrentQueue<int>();
        var queue2 = new Queue<int>();

        // This will work fine.

        var task1 = Enumerable.Range(0, 40)
            .Select(_ => Task.Run(() => producer(item => queue1.Enqueue(item))))
            .ToArray();

        Task.WaitAll(task1);

        // This will cause an exception.

        var task2 = Enumerable.Range(0, 40)
                        .Select(_ => Task.Run(() => producer(item => queue2.Enqueue(item))))
                        .ToArray();

        Task.WaitAll(task2);
    }

Thanks again :)

Upvotes: 0

Oleg Tarasov
Oleg Tarasov

Reputation: 1274

When you are using the lock construct, your code effectively executes in sequence, not in parallel. This solution is suitable for the version with simple Queue as it's not thread-safe, but with ConcurrentQueue, using lock kinda defeats the purpose. Remove the lock for ConcurrentQueue, remove the Thread.Sleep, and use 20 threads instead of 2 just for kicks. You can use Parallel.For() method to spawn your tasks.

Parallel.For(0, 20, i => Run());

Upvotes: 1

Matthew Watson
Matthew Watson

Reputation: 109852

The reason for using ConcurrentQueue<T> is to avoid writing your own locking code.

If you have multiple threads adding or removing items from a Queue<T> you are likely to get an exception. Using a ConcurrentQueue<T> will avoid the exceptions.

Here's a sample program which will likely cause an exception when using multiple threads to write to a Queue<T> while it works with a ConcurrentQueue<T>:

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

internal class Program
{
    private static void Main()
    {
        var queue1 = new ConcurrentQueue<int>();
        var queue2 = new Queue<int>();

        // This will work fine.

        var task1 = Task.Run(() => producer(item => queue1.Enqueue(item)));
        var task2 = Task.Run(() => producer(item => queue1.Enqueue(item)));

        Task.WaitAll(task1, task2);

        // This will cause an exception.

        var task3 = Task.Run(() => producer(item => queue2.Enqueue(item)));
        var task4 = Task.Run(() => producer(item => queue2.Enqueue(item)));

        Task.WaitAll(task3, task4);
    }

    private static void producer(Action<int> add)
    {
        for (int i = 0; i < 10000; ++i)
            add(i);
    }
}

Try running it and see what happens.

Upvotes: 2

user585968
user585968

Reputation:

Don't use lock() in conjunction with ConcurrentQueue<> or similar items in that namespace. It's detrimental to performance.

You can use ConcurrentQueue<> safely with multiple threads and have great performance. The same can not be said with lock() and regular collections.

That's why your results are the same.

Upvotes: 2

Related Questions