Один читатель, много писателей

По теме: Как перехватывать исключения из ThreadPool.QueueUserWorkItem?

Я обнаруживаю исключения в фоновых потоках, запущенных ThreadPool.QueueUserWorkItem () и распространяют их в основной поток через общую переменную экземпляра.

Фоновые потоки делают это:

try
{
    ... stuff happens here...
}
catch (Exception ex1)
{
    lock(eLock) 
    {
        // record only the first exception
        if (_pendingException == null) 
            _pendingException = ex1;
    }
}

Есть несколько потенциальных авторов _pendingException - несколько фоновых потоков, поэтому я защищаю его блокировкой.

В основном потоке, должен ли я снимать блокировку перед чтением _pendingException? Или я могу просто сделать это:

if (_pendingException != null)
    ThrowOrHandle(); 

РЕДАКТИРОВАТЬ:
ps: Я бы предпочел НЕ блокировать поток читателя, потому что он находится на горячем пути, и я бы очень, очень часто снимал и снимал блокировку.


person Cheeso    schedule 04.11.2009    source источник
comment
Ваше описание zdnet не так ли?   -  person Spence    schedule 05.11.2009
comment
Вы делаете что-нибудь, за исключением того, что было возвращено обратно? Или вы могли бы использовать bool, чтобы указать, что произошла ошибка?   -  person Cameron MacFarland    schedule 05.11.2009
comment
зднет? Нет, я даже не знаю, что это такое. Умм, арендатор ... я использую исключение ... ну да. Перекидываю из основного потока.   -  person Cheeso    schedule 05.11.2009


Ответы (3)


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

  1. В многоядерных процессорах, не делая переменную изменчивой (или выполняя какую-либо операцию барьера памяти), может быть момент, когда потоки, выполняющиеся на разных ядрах, могут видеть разные значения. (я не уверен, что вызов lock(queue) в рабочем потоке вызовет какую-либо операцию барьера памяти). (update) Вызов lock(queue) в рабочем потоке вызовет операцию барьера памяти, как указано Эрик в комментарии ниже.

<удар> 2. Помните, что Ссылки не являются адресами (от Эрика Липперта) (если вы предполагаете, что ссылки - это 32-разрядные адреса в 32-разрядной среде CLR, которые можно читать атомарно). Реализация ссылок может быть изменена на некоторые непрозрачные структуры, которые не могут быть прочитаны атомарно в будущих версиях CLR (хотя я думаю, что это вряд ли произойдет в обозримом будущем :)), и ваш код сломается.

person Chansik Im    schedule 04.11.2009
comment
Это НЕ ПРАВИЛЬНО. В спецификации четко указано, что чтение и запись ссылок должны быть атомарными во всех реализациях C #. См. Подробности в разделе 5.5 спецификации. - person Eric Lippert; 06.11.2009
comment
Обновлена ​​(вычеркнута) неверная информация, указанная Эриком Липпертом. Спасибо за исправление и прошу прощения за поздний ответ :). Если цифра 1 по-прежнему неверна, дайте мне знать. Еще раз спасибо! - person Chansik Im; 27.01.2012

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

try
{
    ... stuff happens here...
}
catch (Exception ex1)
{
    lock(queue)
    {
        queue.Enqueue(ex1);
        Monitor.PulseAll(queue);
    }
}

И обработать его:


while(!stopped)
    lock (queue)
    {
        while (queue.Count > 0)
            processException(queue.Dequeue());
        Monitor.Wait(queue);
    }
person mfeingold    schedule 04.11.2009
comment
Мне действительно нужно первое исключение - это быстрый отказ. Никакого восстановления. Так что мне все равно, если я пропущу исключения 2..n. Я просто хочу первую. - person Cheeso; 04.11.2009

Чтение и запись в ссылки являются атомарными (см. C # Spec), и я почти уверен, что блокировка действительно создает барьер памяти, так что да, то, что вы делаете, вероятно безопасно.

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

person shf301    schedule 05.11.2009
comment
Проблема в том, что основной поток - это тот, который выполняет чтение этого поля _pendingException. Я бы предпочел НЕ брать блокировку потока чтения, потому что он находится на горячем пути, и я бы очень часто брал и снимал блокировку. - person Cheeso; 05.11.2009
comment
Почему вы не хотите использовать блокировку основного потока? Вы пробовали это и обнаружили, что он слишком медленный или это преждевременная оптимизация? Если это вызывает у вас проблемы, то вы слишком часто опрашиваете этот флаг и должны выбрать другую стратегию уведомления, например вызывать делегата при ошибке, а не опрашивать флаг. - person shf301; 05.11.2009