简体   繁体   中英

Where is the flaw in my algorithm to lock a critical section by a key (string)?

Attempt:

public class KeyLock : IDisposable
{
    private string key; 

    private static ISet<string> lockedKeys = new HashSet<string>();

    private static object locker1 = new object();

    private static object locker2 = new object();

    public KeyLock(string key)
    {
        lock(locker2)
        {
           // wait for key to be freed up
           while(lockedKeys.Contains(key));

           this.lockedKeys.Add(this.key = key);     
        }
    } 

    public void Dispose()
    {
        lock(locker)
        {
            lockedKeys.Remove(this.key);
        }
    }
}

to be used like

using(new KeyLock(str))
{
    // section that is critical based on str
}

I test by firing the method twice in the same timespan

private async Task DoStuffAsync(string str)
{
    using(new KeyLock(str))
    {
       await Task.Delay(1000);
    }         
}

// ...

await Task.WhenAll(DoStuffAsync("foo"), DoStuffAsync("foo"))

but, strangely enough, when I debug I see that the second time it goes straight through the lock and in fact somehow lockedKeys.Contains(key) evaluates to false even through I can see in my debugger windows that the key is there.

Where is the flaw and how do I fix it?

Take a look at lock statement (C# Reference)

It basically breaks down to

object __lockObj = x;
bool __lockWasTaken = false;
try
{
    System.Threading.Monitor.Enter(__lockObj, ref __lockWasTaken);
    // Your code...
}
finally
{
    if (__lockWasTaken) System.Threading.Monitor.Exit(__lockObj);
}

Enter(Object)

Acquires an exclusive lock on the specified object .


What you need to do instead, is keep around and obtain the same reference. You could probably use a thread safe Dictionary ConcurrentDictionary

public static ConcurrentDictionary<string, object> LockMap = new ConcurrentDictionary<string, object> ();

...

lock (LockMap.GetOrAdd(str, x => new object ()))
{
    // do locky stuff
}

Note : This is just one example of many ways to do this, you will obviously need to tweak it for your needs

The main problems that I notice are as follows:

※Super dangerous infinite loop in the constructor, and super wasteful as well.
※When accessing the private field lockedKeys , you use different objects to lock on→ Not good

However, why your code does not seem to be working I think is because of the short delay you set. Since it is only 1 second of delay during the debugging when you step from statement to statement, 1 second already passes and it gets disposed.

using(new KeyLock(str)){
    await Task.Delay(1000);
}

Luckily for you, I came across a similar problem before and I have a solution, too. Look here for my small solution.

Usage:

//Resource to be shared
private AsyncLock _asyncLock = new AsyncLock();
....
....
private async Task DoStuffAsync()
{
    using(await _asyncLock.LockAsync())
    {
        await Task.Delay(1000);
    }         
} 

// ...

await Task.WhenAll(DoStuffAsync(), DoStuffAsync())

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM