Search code examples
c#multithreadingconcurrencythread-safety

Counter value is not decreasing while using multithreaded loop


I am working on a multi-threaded application using Task.WhenAll() to process multiple requests concurrently. However, I’m encountering unexpected behavior where shared data is being corrupted, possibly due to a race condition.

Here’s the code:

public class DataProcessor
{
    private static int _counter = 0;

    public async Task ProcessDataAsync()
    {
        var tasks = new List<Task>();

        for (int i = 0; i < 1000; i++)
        {
            tasks.Add(Task.Run(() => IncrementCounter()));
        }

        await Task.WhenAll(tasks);
    }

    private void IncrementCounter()
    {
        _counter++;
    }
}

After running this code, the value of _counter is often less than 1000, indicating that some increments might be missed. How can I fix this issue and ensure thread-safe operations in this multi-threaded context?

I have no idea for it now


Solution

  • The issue here is that _counter++ is not thread-safe. It seems like a simple increment operation, but in reality, it involves three steps:

    1. Read the value of _counter.
    2. Increment the value.
    3. Write the updated value back to _counter.

    In a multi-threaded environment, multiple threads can read and increment _counter simultaneously, leading to missed updates and race conditions. This is why the final value of _counter is often less than expected.

    Use Interlocked for Atomic Operations: The Interlocked class provides atomic operations for variables that are shared across threads. Interlocked.Increment ensures that the increment operation is atomic and thread-safe. It avoids race conditions without needing explicit locks.

    Interlocked Class API - Microsoft

    using System;
    using System.Collections.Generic;
    using System.Threading;
    using System.Threading.Tasks;
    
    public class DataProcessor
    {
        // Shared counter, incremented atomically
        private static int _counter = 0;
    
        public async Task ProcessDataAsync()
        {
            const int taskCount = 1000;
            var tasks = new List<Task>(taskCount);
    
            // Add tasks to increment the counter
            for (int i = 0; i < taskCount; i++)
            {
                tasks.Add(Task.Run(IncrementCounter));
            }
    
            // Wait for all tasks to complete
            await Task.WhenAll(tasks);
    
            Console.WriteLine($"Final Counter Value: {_counter}");
        }
    
        private void IncrementCounter()
        {
            Interlocked.Increment(ref _counter); // Atomic increment
        }
    
        // Entry point to test the code
        public static async Task Main(string[] args)
        {
            var processor = new DataProcessor();
            await processor.ProcessDataAsync();
        }
    }