c # блокировка по ссылке, переданной методу - плохая практика? - PullRequest
15 голосов
/ 16 августа 2011

У меня есть метод, похожий на:

public static void DoSomething (string param1, string param2, SomeObject o) 
{
   //.....

   lock(o) 
   {
       o.Things.Add(param1);
       o.Update();
       // etc....
   }
}

Несколько баллов:

  1. Блокировка таким образом плохая практика?
  2. Должен ли я вместо этого заблокировать private static object?
  3. Если так, то почему?

Ответы [ 3 ]

20 голосов
/ 16 августа 2011

Чтобы свести к минимуму побочные эффекты, блокируемый объект должен быть не объектом, которым манипулируют, а отдельным объектом, предназначенным для блокировки.

В зависимости от ваших требований, есть несколько вариантов решения этой проблемы:

Вариант A: Частный объект блокировки

Выберите это, если вы просто хотите убедиться, что DoSomething не конфликтует с параллельным экземпляром DoSomething.

private static readonly object doSomethingLock = new object();

public static void DoSomething (string param1, string param2, SomeObject o) 
{
   //.....

   lock(doSomethingLock) 
   {
       o.Things.Add(param1);
       o.Update();
       // etc....
   }
}

Вариант B: передать блокирующий объект в качестве параметра

Выберите этот параметр, если доступ к o должен быть потокобезопасным даже за пределами DoSomething, т. Е. Если существует вероятность, что кто-то другой напишет метод DoSomethingElse, который работает параллельно DoSomething и который не должен вмешиваться в блок lock в DoSomething:

public static void DoSomething (string param1, string param2, SomeObject o, object someObjectLock) 
{
   //.....

   lock(someObjectLock) 
   {
       o.Things.Add(param1);
       o.Update();
       // etc....
   }
}

Вариант C: создание свойства SyncRoot

Если у вас есть контроль над реализацией SomeObject, может быть удобно предоставить объект блокировки как свойство. Таким образом, вы можете реализовать вариант B без необходимости передавать второй параметр:

class SomeObject
{
    private readonly object syncRoot = new object();

    public object SyncRoot { get { return syncRoot; } }

    ...
}

Тогда вы просто используете lock(o.SyncRoot) в DoSomething. Это шаблон, который используют некоторые классы BCL, например, Array.SyncLock , ICollection.SyncRoot .

2 голосов
/ 16 августа 2011

Просто отвечая на ваш 3-й вопрос:

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

public void XXX(object o)
{
    lock(o)
    {

    }
}

Вам будет трудно попытаться увидетьесли есть тупик.Вам нужно проверить, что объект, переданный в качестве параметра в SomeObject o, никогда не передается в качестве параметра объекту o одновременно.

0 голосов
/ 16 августа 2011

Вот пример того, как вы должны использовать блокировку:

class Account
{
    decimal balance;
    private Object thisLock = new Object();

    public void Withdraw(decimal amount)
    {
        lock (thisLock)
        {
            if (amount > balance)
            {
                throw new Exception("Insufficient funds");
            }
            balance -= amount;
        }
    }
}

А это значит, что вы блокируете объект, который является частной переменной и используется только для блокировки и ничего больше,

Вы можете посмотреть на это:

Заявление о блокировке (C # Reference)

и

Синхронизация потоков (C # и Visual Basic)

...