1

I need to share some locks (aka "object" fields) and regular data fields between functions called from a Transient service in .NET Core. Naturally, these locks and fields should be declared in a thread-safe manner.

What would be the best way to approach it? I am thinking of a separate Singleton service. Do I have to add some keywords to the fields and locks declared so these are thread-safe?

I am well familiar with Java multithreading but never done it so far in C#.

onkami
  • 8,791
  • 17
  • 90
  • 176
  • 1
    For thread safe handling you could take a look at `Monitor` and `lock` - https://stackoverflow.com/questions/4978850/monitor-vs-lock – smoksnes Mar 04 '20 at 13:38

1 Answers1

2

This is the most simple example I can think of. It uses lock. You can also use Monitor. Basically I resolve a transient service 3 times. And then start a couple of threads which will increase the value of a shared service.

class Program
{
    static async Task Main(string[] args)
    {
        var serviceCollection = new ServiceCollection();
        serviceCollection.AddSingleton<ValueService>();
        serviceCollection.AddTransient<Service>();
        var provider = serviceCollection.BuildServiceProvider();

        var serviceOne = provider.GetRequiredService<Service>();
        var serviceTwo = provider.GetRequiredService<Service>();
        var serviceThree = provider.GetRequiredService<Service>();

        // Manipulate the same object 1500 times, from different threads.
        var task1 = serviceOne.DoStuff(500);
        var task2 = serviceTwo.DoStuff(500);
        var task3 = serviceThree.DoStuff(500);

        // Wait for all the threads to complete.
        await Task.WhenAll(task1, task2, task3);

        // Verify the result.
        var valueService = provider.GetRequiredService<ValueService>();
        Console.WriteLine(valueService.SomeValue);
        Console.ReadKey();
    }
}

internal class Service
{
    private readonly ValueService _service;

    public Service(ValueService service)
    {
        _service = service;
    }

    public Task DoStuff(int noOfTimes)
    {
        var tasks = new Task[noOfTimes];

        for (int i = 0; i < noOfTimes; i++)
        {
            tasks[i] = Task.Run(() =>
            {
                Thread.Sleep(100);
                _service.Increase();
            });
        }

        return Task.WhenAll(tasks);
    }

}

internal class ValueService
{
    public void Increase()
    {
        // Use lock to make sure that only one thread is changing the field at the time.
        // Remove the lock statement and you will notice some "unwanted" behaviour.
        lock (_lock) 
        {
            SomeValue++;
        }

        // Alternatively you can use Interlocked.Increment(SomeValue)
    }

    private readonly object _lock = new object();
    public int SomeValue { get; private set; }
}
smoksnes
  • 10,509
  • 4
  • 49
  • 74
  • As ValueService is a singleton you don't need to mark the _lock field as static. All threads will use the same value due to it being a singleton – Richard Blewett Mar 04 '20 at 16:29
  • @RichardBlewett yes, that is true. I've updated the example. – smoksnes Mar 05 '20 at 06:23
  • Many thanks. Could you possibly comment: is "get" operation thread-consistent, in this case (so if I try to set, get will be getting what other thread is setting?), and why there is no use of ```volatile``` ? – onkami Mar 05 '20 at 09:33
  • @AskarIbragimov `volatile` may be used, but it doesn't replace `lock`. Regarding `volatile` I suggest you rean on [here](https://stackoverflow.com/a/19384758/4949005). Currently `get` is not thread safe since it may return the value that is being written by another thread. You may want to lock that too. You can use the same `lock` in both `get` and `set`. – smoksnes Mar 05 '20 at 10:08
  • In that case perhaps you should amend example to include lock on read. – onkami Mar 05 '20 at 13:14
  • @AskarIbragimov applying lock in the property could be quite harmful in this case. Since the lock is released after `get` is finished another thread may change the value before `set` is used. Therefore you should do your manipulation within the lock. A good answer about this can be found [here](https://stackoverflow.com/a/7161563/4949005). – smoksnes Mar 05 '20 at 14:12