3

I have MVC application in which action methods must be executed on certain order. Recently, I am having some strange issues and I suppose that it is due to the fact that I do not do any thread synchronization. I have barely worked with multithreading and I don't know much about it. I tried to implement some kind of locking where I have to lock according to Id. So I implemented class like below to get required lock objects.

public class ReportLockProvider
    : IReportLockProvider
{
    readonly ConcurrentDictionary<long, object> lockDictionary 
        = new ConcurrentDictionary<long, object>();

    public object ProvideLockObject(long reportId)
    {
        return lockDictionary.GetOrAdd(reportId, new object());
    }
}

I tried to use this as below:

ReportLockProvider lockProvider = new ReportLockProvider();

public async ActionResult MyAction(long reportId)
{
    lock(lockProvider.ProvideLockObject(reportId))
    {
        // Some operations
        await Something();
        // Some operation
    }
}

I hoped that it would work, but it event didn't compiled because I have used await inside lock body. I have searched a bit and came across to SemaphoreSlim in this answer. Now, the problem is that I have to get lock object according to Id. How can I do this? Is it OK to create multiple SemaphoreSlim objects? Is it OK if I modify code like below :

public class ReportLockProvider
    : IReportLockProvider
{
    readonly ConcurrentDictionary<long, SemaphoreSlim> lockDictionary 
        = new ConcurrentDictionary<long, SemaphoreSlim>();

    public SemaphoreSlim ProvideLockObject(long reportId)
    {
        return lockDictionary.GetOrAdd(reportId, new SemaphoreSlim(1, 1));
    }
}


public async ActionResult MyAction(long reportId)
{
    var lockObject = ReportLockProvider.ProvideLockObject(reportId);

    await lockObject.WaitAsync();
    try
    {
        // Some operations
        await Something();
        // Some operation
    }
    finally
    {
        lockObject.Release();
    }
}

The other question is that, can I use SemaphoreSlim in non-async methods? Is there any better option?

Adil Mammadov
  • 8,476
  • 4
  • 31
  • 59
  • If your code is working, you may be better off posting to https://codereview.stackexchange.com/ (read their rules first, I'm not sure if this is on topic there because I'm not a contributor) – DavidG Jul 20 '17 at 12:14
  • @DavidG, thanks for offer, but I am not sure if will work properly or not. – Adil Mammadov Jul 20 '17 at 12:29
  • @AdilMammadov Then test the code and see if it works or not. If it does work, ask for a code review, if it doesn't, and you can't figure out how to fix the problem that you're having, then you could ask about the problem you're having. – Servy Jul 20 '17 at 14:03
  • @Servy, when I mean *working or not* I do not mean *compiling* or *workds when I test it*. As I mentioned I haven't worked with multithreading. What to you propose, to apply changes and publish to production and wait for users to get crazy? DavidG has already proposed it an I will think about moving it to code review. If you have any suggestion then make it, otherwise go and be happy with your downvotes – Adil Mammadov Jul 20 '17 at 14:10
  • @AdilMammadov Why are you equating "test the code" with "deploy it to production without testing it"? When I say test the code I don't mean deploy it to production without testing it, no. – Servy Jul 20 '17 at 14:20
  • @Servy, then how can I test if it creates deadlocks, memory exceptions, or any sort of problems while used by thousands of users? I even do not know if using `SemaphoreSlim` like above is acceptable or not. Also, I am asking if there is any better option or not? – Adil Mammadov Jul 20 '17 at 14:22
  • Can you post the code that actually causes the issue? Was `ReportLockProvider` susposed to solve some issue? The only reference to your problem is that _action methods must be executed on certain order_, can your provide an example of what it is your trying to accomplish? – JSteward Jul 20 '17 at 17:00
  • @JSteward, I think I wasn not able to explain the situation. The problem is that sometimes I have strange values on tables without any exception. It is very rare about 1/10000. So I think the problem can be caused by user somehow executes another action method before one ends. So the problem has nothing to do with the question, my question is how can I implement locking appropriately and preserve async/await? – Adil Mammadov Jul 20 '17 at 20:11

1 Answers1

0

I think you are missing a static keyword in front of your lockDictionary, but it depends on how you instanciate the provider.

Here is a sample with a little change code I cooked up in LinqPad:

async Task Main()
{
    ReportLockProvider reportLockProvider = new ReportLockProvider();
    List<Task> tasks = new List<Task>(10);

    for (long i = 1; i <= 5; i++) {
        var local = i;
        tasks.Add(Task.Run(() => Enter(local) ));
        tasks.Add(Task.Run(() => Enter(local) ));
    }

    async Task Enter(long id)
    {
        Console.WriteLine(id + " waiting to enter");
        await reportLockProvider.WaitAsync(id);

        Console.WriteLine(id + " entered!");
        Thread.Sleep(1000 * (int)id);

        Console.WriteLine(id + " releasing");
        reportLockProvider.Release(id);
    }

    await Task.WhenAll(tasks.ToArray());
}

public class ReportLockProvider
{
    static readonly ConcurrentDictionary<long, SemaphoreSlim> lockDictionary = new ConcurrentDictionary<long, SemaphoreSlim>();

    public async Task WaitAsync(long reportId)
    {
        await lockDictionary.GetOrAdd(reportId, new SemaphoreSlim(1, 1)).WaitAsync();
    }
    public void Release(long reportId)
    {
        SemaphoreSlim semaphore;
        if (lockDictionary.TryGetValue(reportId, out semaphore))
        {
            semaphore.Release();
        }
    }
}
Thomas
  • 194
  • 4
  • Thanks for your answer. Yes, actually I forgot `static` keyword. Is it OK to use `SemaphoreSlim` this way? – Adil Mammadov Jul 20 '17 at 14:02
  • I would proberbly look into Symphore or a Mutex instead they can be named across threads and applications on a machine, but they work in a difference manner, as they are not awaitable so your code has to handle that differently. so it kind of depends on what you want to achive inside the actual logic. – Thomas Jul 20 '17 at 14:24
  • also if you go with the above solution you need to do cleanups on the dictionary objects some how or you run into memory issues at some point – Thomas Jul 20 '17 at 14:25
  • I think AppPool recycling will clean the dictionary for me. I will search a bit and will let you know about result. Adding `WaitAsync` and `Release` methods to `ReportLockProvider` is good idea. – Adil Mammadov Jul 20 '17 at 14:39
  • i tried this implementation , and i found that is has problem when you have lot of requests at the same time. it ends up by loosing some requests. i replaced if (lockDictionary.TryGetValue(reportId, out semaphore)) with – Baouche Iqbal Mar 11 '22 at 20:49
  • 1
    `lockDictionary.GetOrAdd(reportId, new SemaphoreSlim(1, 1))` will create a new SemaphoreSlim instance every request, I think should use `lockDictionary.GetOrAdd(reportId, _ => new SemaphoreSlim(1, 1))` instead – Minh Giang Jun 15 '23 at 04:12