I am playing around with Interlocked.Increment
and was wondering which of the call counter impl. below (to basically get the number of call of a given delegate) is doing its job properly (ie. thread-safe) in a highly concurrent environment.
public interface ICallCounter<in TInput, out TOutput>
{
public TOutput Invoke(TInput input);
public int Count { get; }
}
public class InterlockedPreCallCounter<TInput, TOutput> : ICallCounter<TInput, TOutput>
{
private readonly Func<TInput, TOutput> _func;
private int _count;
public int Count => _count;
public InterlockedPreCallCounter(Func<TInput, TOutput> func) => _func = func;
public TOutput Invoke(TInput input)
{
Interlocked.Increment(ref _count);
// What if there is an interruption / crash at some point here?
return _func(input);
}
}
public class InterlockedPostCallCounter<TInput, TOutput>
{
private readonly Func<TInput, TOutput> _func;
private int _count;
public int Count => _count;
public InterlockedPostCallCounter(Func<TInput, TOutput> func) => _func = func;
public TOutput Invoke(TInput input)
{
var result = _func(input);
Interlocked.Increment(ref _count);
return result;
}
}
public class LockCallCounter<TInput, TOutput> : ICallCounter<TInput, TOutput>
{
private readonly Func<TInput, TOutput> _func;
public int Count { get; private set; }
private readonly object _syncRoot = new object();
public LockCallCounter(Func<TInput, TOutput> func) => _func = func;
public TOutput Invoke(TInput input)
{
lock (_syncRoot)
{
var result = _func(input);
Count++;
return result;
}
}
}
All methods are completely thread-safe, with respect to _count
.
However, this will increment _count
irrespective as to whether _func
throws an exception:
Interlocked.Increment(ref _count);
return _func(input);
This will increment _count
only if _func
does not throw an exception.
var result = _func(input);
Interlocked.Increment(ref _count);
return result;
And this will do the same as the above, but with poorer performance in a multi-threaded environment, particularly because only one thread will be able to call _func
at any one time:
lock (_syncRoot)
{
var result = _func(input);
Count++;
return result;
}
Which you choose depends on what you are trying to measure.