Reputation:
From the documentation here: "The methods of this class help protect against errors that can occur when the scheduler switches contexts while a thread is updating a variable that can be accessed by other threads..."
Also, an answer to this question states "INTERLOCKED METHODS ARE CONCURRENTLY SAFE ON ANY NUMBER OF COREs OR CPUs" which seems pretty clear.
Based on the above I thought Interlocked.Add() would be sufficient for multiple threads to do addition on a variable. Apparently I'm wrong or I'm using the method incorrectly. In the runnable code below I expect Downloader.ActiveRequestCount to be zero when Run() completes. If I do not lock around the call to Interlocked.Add I get a random non-zero result. What is the correct usage of Interlocked.Add()?
class Program
{
private Downloader downloader { get; set; }
static void Main(string[] args)
{
new Program().Run().Wait();
}
public async Task Run()
{
downloader = new Downloader();
List<Task> tasks = new List<Task>(100);
for (int i = 0; i < 100; i++)
tasks.Add(Task.Run(Download));
await Task.WhenAll(tasks);
Console.Clear();
//expected:0, actual when lock is not used:random number i.e. 51,115
Console.WriteLine($"ActiveRequestCount is : {downloader.ActiveRequestCount}");
Console.ReadLine();
}
private async Task Download()
{
for (int i = 0; i < 100; i++)
await downloader.Download();
}
}
public class Downloader :INotifyPropertyChanged
{
private object locker = new object();
private int _ActiveRequestCount;
public int ActiveRequestCount { get => _ActiveRequestCount; private set => _ActiveRequestCount = value; }
public async Task<string> Download()
{
string result = string.Empty;
try
{
IncrementActiveRequestCount(1);
result = await Task.FromResult("boo");
}
catch (Exception ex)
{
Console.WriteLine("oops");
}
finally
{
IncrementActiveRequestCount(-1);
}
return result;
}
public void IncrementActiveRequestCount(int value)
{
//lock (locker) // is this redundant
//{
_ActiveRequestCount = Interlocked.Add(ref _ActiveRequestCount, value);
//}
RaisePropertyChanged(nameof(ActiveRequestCount));
}
#region INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged([CallerMemberNameAttribute] string propertyName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
#endregion
}
Upvotes: 1
Views: 1350
Reputation: 172230
Replace
_ActiveRequestCount = Interlocked.Add(ref _ActiveRequestCount, value);
with
Interlocked.Add(ref _ActiveRequestCount, value);
Interlocked.Add is thread-safe and takes a ref
parameter, so that it can do the assignment safely. You additionally perform an (unnecessary) unsafe assignment (=
). Just remove it.
Upvotes: 3