Search code examples
c#multithreadinglockingstaleobjectstate

Threading - Guarantee getting the last result


I have several threads which call a method. It looks something like this:

public void DoWork(params int[] initialConditions)
{
    //Do a lot of work
}

However, If conditions are changing quickly, I get a lot of stale intermediate results because I can't finish the calculations quickly enough. I know! I'll modify the code to look like this:

public void DoWork(params int[] initialConditions)
{
    if(Monitor.TryEnter(syncLock)
    {
        //Do a lot of work
        Monitor.Exit(syncLock);
    }
}

Now I won't bother to do calculations unless my previous calculations are finished. I'll be a little behind in rapidly changing situations, but no more than I would have been, and I won't waste my time doing all that extra work for stale results.

However,

Once the situation stops changing, I'm still a little bit out of date, and the final thread that wanted to call DoWork is long gone. Is there a way to tell a thread:

if no one is doing work
    do work
else
    wait to do work until after the other thread finishes
but
    if a new thread arrives before you start doing work, leave without doing work.

Solution

  • This code should meet your requirements described by pseudo code:

    class Program
    {
        static object _workLockObject = new object();
        static volatile int _currentPriority = 0;
    
        static void Main()
        {
            Task t1 = new Task(() => TryDoWork(1));
            Task t2 = new Task(() => TryDoWork(2));
            Task t3 = new Task(() => TryDoWork(3));
    
            t1.Start();
            Thread.Sleep(100);
            t2.Start();
            Thread.Sleep(100);
            t3.Start();
            Console.ReadKey();
        }
        public static void TryDoWork(params int[] initialConditions)
        {
            var priotity = Interlocked.Increment(ref _currentPriority);
            while (!Monitor.TryEnter(_workLockObject))// starting to wait when DoWork is available
            {
                if (priotity != _currentPriority) // if the thread has stale parameters
                {
                    Console.WriteLine("DoWork skipped " + initialConditions[0]);
                    return;// skipping Dowork
                }
                Thread.Sleep(300);// Change the interval according to your needs
            }
            try // beginning of critical section
            {
                if (priotity == _currentPriority) // if the thread has the newest parameters
                    DoWork(initialConditions);
            }
            finally
            {
                Monitor.Exit(_workLockObject); // end of critical section
            }
        }
        public static void DoWork(params int[] initialConditions)
        {
            Console.WriteLine("DoWork started " + initialConditions[0]);
            Thread.Sleep(5000);
            Console.WriteLine("DoWork ended " + initialConditions[0]);
        }
    }
    

    Interlocked.Increment guarantees that every thread has its own priority and the latest thread has a priority that allows to execute DoWork when it is available.