Где в моем алгоритме изъян для блокировки критического раздела ключом (строкой)? - PullRequest
0 голосов
/ 05 октября 2018

Попытка:

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);
        }
    }
}

, которая будет использоваться как

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

Я проверяю, дважды запуская метод за один и тот же промежуток времени

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

// ...

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

но, как ни странноДостаточно, когда я отлаживаю, я вижу, что во второй раз он проходит прямо через lock и на самом деле как-то lockedKeys.Contains(key) оценивается до false, даже когда я вижу в своих окнах отладчика, что ключ есть.

Где недостаток и как его исправить?

Ответы [ 2 ]

0 голосов
/ 05 октября 2018

Основные проблемы, которые я замечаю, следующие:

※ Супер опасный бесконечный цикл в конструкторе, а также супер расточительный.
※ При доступе к закрытому полю lockedKeys вы используете разныеобъекты для блокировки → Нехорошо

Однако, почему ваш код не работает, я думаю, что это из-за небольшой установленной вами задержки.Поскольку при переходе от оператора к утверждению задержка составляет всего 1 секунду, уже проходит 1 секунда, и она утилизируется.

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

К счастью для вас, я сталкивался с подобной проблемой раньше, и яесть решение тоже.Посмотрите здесь для моего небольшого решения.

Использование:

//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())
0 голосов
/ 05 октября 2018

Взгляните на оператор блокировки (C # Reference)

В основном он разбивается на

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)

Получает эксклюзивную блокировку для указанного объекта .


Что вам нужно сделать вместо этого, это сохранитьвокруг и получить ту же ссылку.Возможно, вы могли бы использовать потокобезопасный словарь ConcurrentDictionary

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

...

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

Примечание : это всего лишь один из многих способов сделать это, вам, очевидно, потребуется настроить егодля ваших нужд

...