is there a xor style or multi type lock in c?

If you are just looking for exclusion and you are not needing to ensure instruction order and specific read write memory barriers. Then I give the worlds most convoluted example.

Basically it’s just an encapsulated way of queueing up async workloads via a TaskCompletionSource to run exclusively by type. It’s fairly light-weight, it will work well with the thread pool and work for multiple types.

Given the classes

public class Base
{
   public int Id { get; set; }
}

public class Bob:Base { }
public class Cat:Base { }
public class Flib:Base { }

Some sort of workload

public static async Task SharedAsync<T>(T instance) 
   where T : Base 
{
   await _xor.WaitAsync(instance);
   try
   {
      Console.WriteLine("Starting " + typeof(T) + " " + instance.Id);
      await Task.Delay(100);
   }
   finally
   {
      Console.WriteLine("Fishing " + typeof(T) + " " + instance.Id);
      _xor.Release(instance); 
   }
}

Usage

_xor = new XorLimiter(typeof(Bob), typeof(Cat), typeof(Flib));

static Task Generate(int x)
   => _r.Next(0, 3) switch
   {
      0 => SharedAsync(new Bob() {Id = x}),
      1 => SharedAsync(new Cat() {Id = x}),
      2 => SharedAsync(new Flib() {Id = x}),
      _ => throw new ArgumentOutOfRangeException()
   };

var tasks = Enumerable
   .Range(0, 15)
   .Select(Generate);

await Task.WhenAll(tasks);

Console.WriteLine("Finished");

Output

Starting Program+Bob 0
Starting Program+Bob 3
Starting Program+Bob 7
Starting Program+Bob 8
Starting Program+Bob 9
Starting Program+Bob 14
Fishing Program+Bob 9
Fishing Program+Bob 7
Fishing Program+Bob 8
Fishing Program+Bob 14
Fishing Program+Bob 3
Fishing Program+Bob 0
Starting Program+Flib 1
Starting Program+Flib 2
Starting Program+Flib 5
Starting Program+Flib 10
Fishing Program+Flib 10
Fishing Program+Flib 5
Fishing Program+Flib 1
Fishing Program+Flib 2
Starting Program+Cat 4
Starting Program+Cat 6
Starting Program+Cat 11
Starting Program+Cat 12
Starting Program+Cat 13
Fishing Program+Cat 12
Fishing Program+Cat 13
Fishing Program+Cat 6
Fishing Program+Cat 11
Fishing Program+Cat 4
Finished

Full Demo here


XorLimiter Class

public class XorLimiter
{
   private readonly Type[] _types;

   private readonly object _sync = new object();
   private Type _current;
   private readonly Queue<Type> _next = new Queue<Type>();
   private readonly Dictionary<object, int> _processing = new Dictionary<object, int>();
   private readonly Dictionary<Type,List<(object instance, TaskCompletionSource<bool> source)>> _waiting = new Dictionary<Type, List<(object instance, TaskCompletionSource<bool> source)>>();

   public XorLimiter(params Type[] types) => _types = types;

   private void Process()
   {

      if (_processing.Any() || !_next.Any()) return;
      _current = _next.Dequeue();

      if(!_waiting.TryGetValue(_current,out var list))
         throw new InvalidOperationException("Nothing to process");

      foreach (var (instance, source) in list)
      {
         AddOrUpdate(instance);
         source.TrySetResult(true);
      }

      _waiting.Remove(_current);
   }

   public void Release<T>(T instance)
   {

      if(!_types.Contains(typeof(T)))
         throw new InvalidOperationException("Not monitoring type : " + typeof(T).Namespace);

      lock (_sync)
      {
         try
         {
            if (!_processing.ContainsKey(instance))
               throw new InvalidOperationException("Instance does not exists");

            _processing[instance]--;

            if (_processing[instance] < 0)
               throw new InvalidOperationException("Instance count is less than 0");

            if (_processing[instance] == 0)
               _processing.Remove(instance);

         }
         finally
         {
            Process();
         }
      }
   }

   private void AddOrUpdate<T>(T instance)
   {
      if (_processing.ContainsKey(instance))
         _processing[instance]++;
      else
         _processing.Add(instance,1);
   }
   public ValueTask WaitAsync<T>(T instance)
   {

      if(!_types.Contains(typeof(T)))
         throw new InvalidOperationException("Not monitoring type : " + typeof(T).Namespace);

      lock (_sync)
      {
         try
         {

            _current ??= typeof(T);

            if (_current ==typeof(T))
            {
               AddOrUpdate(instance);
               return new ValueTask();
            }

            var tcs = new TaskCompletionSource<bool>();
            if (!_waiting.TryGetValue(typeof(T), out var list))
            {
    
               _waiting.Add(typeof(T), list = new List<(object instance, TaskCompletionSource<bool> source)>());
               _next.Enqueue(typeof(T));

            }
            list.Add((instance,tcs));
            return new ValueTask(tcs.Task);

         }
         finally
         {
            Process();
         }
      }
   }
}

Note: This is nothing but a trivial example of how you might attack such a problem. There are many many other approaches, and this is not meant to be peer reviewed code or the bastion of perfect or lock-free code. This has only been tested minimally and lacks a lot of fault tolerance, and basic functionality like cancelation tokens and sanctity checking.

CLICK HERE to find out more related problems solutions.

Leave a Comment

Your email address will not be published.

Scroll to Top