Как бороться с условиями гонки в многопоточности? - PullRequest
5 голосов
/ 27 сентября 2011

Вот пример:

if (control.InvokeRequired)
{
    control.BeginInvoke(action, control);
}
else
{
    action(control);
}

Что если между условием и вызовом BeginInvoke расположен элемент управления, например?

Другой пример, связанный с событиями:

var handler = MyEvent;

if (handler != null)
{
    handler.BeginInvoke(null, EventArgs.Empty, null, null);
}

Если между первой строкой и оператором if отписано MyEvent, оператор if все равно будет выполнен.Тем не менее, это правильный дизайн?Что, если отмена подписки также приведет к разрушению состояния, необходимого для надлежащего вызова события?Мало того, что это решение имеет больше строк кода (шаблон), но оно не так интуитивно понятно и может привести к неожиданным результатам на стороне клиента.

Что скажете вы, ТАК?

Ответы [ 3 ]

5 голосов
/ 27 сентября 2011

По моему мнению, если что-то из этого является проблемой, как управление потоками, так и управление жизненным циклом объектов безрассудны и требуют пересмотра.

В первом примере код не является симметричным: BeginInvoke не будет ждать завершения action, но прямой вызов будет; это, наверное, уже ошибка.

Если вы ожидаете, что еще один поток потенциально удастся утилизировать элемент управления, с которым вы работаете, у вас нет другого выбора, кроме как перехватить ObjectDisposedException - и он не может быть выдан, пока вы уже не внутри action, и, возможно, не в текущей теме благодаря BeginInvoke.

Неправильно предполагать, что, как только вы отменили подписку на событие, вы больше не будете получать уведомления о нем. Для этого даже не требуется нескольких потоков - только несколько подписчиков. Если первый подписчик делает что-то во время обработки уведомления, в результате чего второй подписчик отменяет подписку, уведомление, находящееся в данный момент «в полете», все равно будет отправлено второму подписчику. Возможно, вам удастся смягчить это с помощью защитного предложения в верхней части подпрограммы обработчика событий, но вы не можете остановить его.

3 голосов
/ 27 сентября 2011

Существует несколько методов для разрешения состояния гонки:

  • Оберните все это мьютексом.Убедитесь, что есть блокировка, которую каждый поток должен сначала получить, прежде чем он сможет даже начать работать в гонке.Таким образом, как только вы получите блокировку, вы узнаете, что никакой другой поток не использует этот ресурс, и вы можете безопасно завершить его.
  • Найдите способ обнаружить и восстановить их;Это может быть очень сложно, но некоторые виды приложений работают хорошо;Типичный способ справиться с этим - вести учет количества изменений ресурса;Если вы закончили с задачей и обнаружили, что номер версии отличается от того, который вы начали, прочитайте новую версию и запустите задачу заново с самого начала (или просто не удалось)
  • измените дизайн приложения, чтобы использовать только атомарныйдействия;в основном это означает использование операций, которые могут быть выполнены за один шаг;это часто включает операции «сравнить и поменять» или поместить все данные транзакции в один дисковый блок, который может быть записан атомарно.
  • перепроектировать приложение, чтобы использовать методы без блокировок;Эта опция имеет смысл только тогда, когда удовлетворение жестких ограничений в реальном времени более важно, чем обслуживание каждого запроса, поскольку конструкции без блокировок по своей природе теряют данные (обычно некоторого характера с низким приоритетом).

Какая опция«правильно» зависит от приложения.У каждого варианта есть компромиссы производительности, которые могут сделать преимущество параллелизма менее привлекательным.

0 голосов
/ 27 сентября 2011

Если это поведение распространяется в нескольких местах в вашем приложении, возможно, стоит перестроить API, который выглядит следующим образом:

if(!control.invokeIfRequired()){
    action(action);
}

Точно так же, как стандартная библиотека JDK ConcurrentHashMap.putIfAbsent(...).Конечно, вам нужно иметь дело с синхронизацией внутри этого нового control.invokeIfRequired() метода.

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