How to use parallel tasks that need access to the same ressource.

c#chris

Member
Joined
Dec 10, 2024
Messages
8
Programming Experience
1-3
Hello,

when I try to make my code more effective (faster) via parallel tasks I get Exceptions.
If I remove the parallel tasks everything works as it should.
What I am doing wrong and how can I make my code faster without exceptions.



Here is the code sample:
public class LineManager : INotifyPropertyValueChanged
{

    //.......

    internal List<MyLine> HorizontalLine { get; } = new();

    //.......

    internal void RenderLines(IChart argichart, RenderContext argcontext)
    {
            List<MyLine>? _tempList = null;
            lock (HorizontalLine) _tempList = new(HorizontalLine);
                    foreach (var line in _tempList)
                    {
                        DrawLines(argichart, argcontext, line, line.LineColor, line.LineType);
                    }
    }

    internal void AddLine(MyLine argline)
    {
                lock (HorizontalLine) HorizontalLine.Add(argline);
    }

    //.......
}


internal class MyClass()
{
    public LineManager _lineManager { get; set; } = new();

    private void AddLines()
    {
        _lineManager.AddLine(SupportLine);
    }

    public MyClass() {}

    //Not sure what triggers this but it is used to update GUI
    protected override void OnRender(RenderContext context, DrawingLayouts layout)
    {
        Parallel.Invoke(
                       () => _lineManager.RenderLines(ChartInfo!, context),
                        //and more ...
                    );
    }

    //Called when new data is available
    protected override void OnCalculate(int bar, decimal value)
    {
        Parallel.Invoke(
                       () => AddLines(),
                        //and more ...
                    );
    }
}
 
Last edited by a moderator:
What was the exception you were getting?
 
Last edited:
Hello, see here:

Time Source Message
04.01.2025 18:49:56 Lines.LineManager "DrawLines System.ArgumentException: Destination array was not long enough. Check the destination index, length, and the array's lower bounds. (Parameter 'destinationArray')
at System.Array.CopyImpl(Array sourceArray, Int32 sourceIndex, Array destinationArray, Int32 destinationIndex, Int32 length, Boolean reliable)
at System.Array.Copy(Array sourceArray, Int32 sourceIndex, Array destinationArray, Int32 destinationIndex, Int32 length)
at System.Collections.Generic.Queue`1.SetCapacity(Int32 capacity)
 
Well, that call stack seems to indicate updating a Queue, rather than adding to a List. So it's what ever that is holding that Queue that needs to do some resource locking, or swap to a queue implementation that is thread safe.
 
Do you make significant gains if you parallelize out to multiple threads, all but one of which must then wait for a shared resource, while only one thread works on it?
 
To use parallel tasks that need access to the same resource in C#, you must use synchronization mechanisms like locks (lock keyword) to ensure only one thread can access the shared resource at a time, preventing race conditions; leverage thread-safe collections like ConcurrentDictionary when dealing with data structures that multiple threads need to read/write to;
and utilize the Task Parallel Library (TPL) to manage parallel task efficiently.
lockObject:
A dedicated object used as a lock to synchronize access to the counter variable.
lock (lockObject):
When a thread enters this block, it acquires the lock on lockObject, preventing other threads from accessing the critical section (incrementing counter) until the current thread releases the lock.
Example with a simple lock::
class DataManager

{

    private int counter = 0;

    private object lockObject = new object();



    public void IncrementCounter()

    {

        lock (lockObject)

        {

            counter++;

        }

    }

}



public void ParallelOperations()

{

    var tasks = new List<Task>();

    var dataManager = new DataManager();



    for (int i = 0; i < 10; i++)

    {

        tasks.Add(Task.Run(() => dataManager.IncrementCounter()));

    }



    Task.WaitAll(tasks.ToArray()); // Wait for all tasks to finish

}

Example:
Using ConcurrentDictionary:

var concurrentData = new ConcurrentDictionary<string, int>();

// Multiple tasks can safely read and write to 'concurrentData' concurrently [1, 3, 6]

Task.Run(() => concurrentData.TryAdd("key1", 10));

Task.Run(() => Console.WriteLine(concurrentData.GetValueOrDefault("key1")));

Important considerations to think about regarding performance:

Performance impact:
Excessive locking can negatively impact performance, so try to minimize the critical section of code within the lock block.

Deadlocks:
Be careful when using multiple locks in complex scenarios, as it can lead to deadlocks where threads are waiting for each other to release locks.

Consider alternatives:
Depending on your specific use case, you might explore other synchronization techniques like Monitor class or Semaphore for more granular control.
 
Last edited:

Latest posts

Back
Top Bottom