Самый эффективный способ использовать блокировки для изменения двух связанных полей на основе предыдущих значений? - PullRequest
1 голос
/ 01 апреля 2012

Я знаю, что это более быстрый \ лучший способ использования Interlocked.CompareExchange, но я просто ищу лучший способ, если вы ограничены блокировками.

Джо Албахари говорит следующее:

Также можно безопасно назначить новый объект ProgressStatus на основе его предыдущего значения (например, можно «увеличить» значение PercentComplete) - без блокировки более чем на одну строку кода.

Редактировать: Обновлен код, основанный на ответе @ drch. Ожидание, чтобы увидеть, есть ли способ сделать это с меньшей \ более эффективной блокировкой. Эта блокировка имеет все чтение, запись и новый конструктор в блокировке. Надеялся на меньший \ более эффективный замок. Спасибо.

class ProgressStatus
{
    public readonly int PercentComplete;
    public readonly string StatusMessage;
    public ProgressStatus(int percentComplete, string statusMessage)
    {
        PercentComplete = percentComplete;
        StatusMessage = statusMessage;
    }
}
class Test
{
    readonly object _statusLocker = new object();
    ProgressStatus _status;
    void IncreaseProgress()
    {
        lock (_statusLocker)
          _status = new ProgressStatus(_status.PercentComplete + 10, _status.StatusMessage);
    }
}

Ответы [ 2 ]

1 голос
/ 01 апреля 2012

В статье, которую вы упомянули, только требование блокировки на одной строке ссылалось на продвинутую технику позже в Part 5 .

Относительно кода, который вы имеете выше, возможно получить условие гонки между вашими двумя замками. Два потока могут прочитать одно и то же значение, а затем записать одно и то же значение, когда желаемый результат будет иметь их оба приращения.

Блокировка для всего чтения и записи:

lock (_statusLocker) {
    statusCopy = _status;
    var newStatus = new ProgressStatus(statusCopy.PercentComplete + 10, statusCopy.StatusMessage);
    _status = newStatus;
}

Или просто:

lock (_statusLocker) {
   _status = new ProgressStatus(_status.PercentComplete + 10, _status.StatusMessage);
}
0 голосов
/ 01 апреля 2012

IMO, используя приведенный выше код, вы можете обойтись без каких-либо блокировок, поскольку вы используете неизменяемый класс и работаете только с копиями данных. Спецификации C # гарантируют, что чтение и запись ссылок будут атомарными.

Редактировать: Однако синхронизация потоков - это b ***, поэтому я хотел бы услышать еще несколько мнений ...

Редактировать: Также, как я указывал в комментариях, в коде есть недостаток, если более чем один поток вызывает метод. В этом случае обновления могут быть потеряны.

...