Search code examples
c#multithreadingparallel-processingthread-safetythreadpool

How to lock threads dynamically and avoid race condition


I'm trying to lock threads dynamically, but no matter what I try, there's always some sort of racecondition happening, it seems to be caused by multiple threads started the tasks simultaneously, but I haven't been able to find a good answer to this.

Here is an example:

using System.Linq;
using System.Threading;
using System.Threading.Tasks;

public class Example
{
public static readonly Object padLock = new Object();
public readonly static ConcurrentDictionary<string, object> locks = new 
ConcurrentDictionary<string, object>();
public static List<string> processes = new List<string>();

public static void Main()
{
  //Add random list of processes (just testing with one for now)
  for (var i = 0; i < 1; i++) {
     processes.Add("random" + i.ToString()); 
  }

  while (true)
  {
     foreach (var process in processes )
     {
        var currentProc = process ;
        lock (padLock)
        {
           if (!locks.ContainsKey(currentProc))
           {
              System.Threading.Tasks.Task.Factory.StartNew(() =>
              {
                 if (!locks.ContainsKey(currentProc))
                 {
                    var lockObject = locks.GetOrAdd(currentProc, new object());
                    lock (lockObject)
                    { 
                       Console.WriteLine("Currently Executing " + currentProc); 
                       Console.WriteLine("Ended Executing " + currentProc);
                       ((IDictionary)locks).Remove(currentProc);
                    }
                 }
              });
           }
        }
       // Thread.Sleep(0);
     }
  }

  Console.ReadLine();
 }
}

OUTPUT:

Started 1

Finished 1

Started 1

Finished 1

Started 1

Finished 1

but sometimes getting:

Started 1

Started 1

Finished 1

That is not desired, the dynamic lock should lock it and execute it just once


Solution

  • This is a common problem

    I read your requirements as "Obtain a list of processes, then do something to each of them only once, using multiple threads."

    For the purposes of my examples, assume Foo(process) does the unit of work that must be done only once.

    This is a very common need and there are several patterns.

    Parallel.ForEach

    This technique can use a different thread for each iteration of the loop, which will execute concurrently.

    Parallel.ForEach(processes, process => Foo(process));
    

    Yes; it's one line of code.

    async Tasks

    This technique is appropriate if Foo() is async. It simply schedules a task for all of the processes then awaits them, and lets the SynchronizationContext sort it out.

    var tasks = processes.Select( p => Foo(process) );
    await Task.WhenAll(tasks);
    

    Producer/Consumer

    This uses the producer-consumer pattern, which is the traditional way for one thread to add to a queue and another to take from it. By removing an item from the queue, it is effectively "locked" so that other threads don't try to work on it.

    BlockingCollection<string>() queue = new BlockingCollection<string>();
    
    void SetUpQueue()
    {
        for (int i=0; i<100; i++) queue.Add(i.ToString());
        queue.CompleteAdding();
    }
    
    void Worker()
    {
        while (queue.Count > 0 || !queue.IsAddingCompleted)
        {
            var item = queue.Take();
            Foo(item);
        }
    }